Bladeren bron

Окончания строк заменены?..

Вадим Королёв 1 jaar geleden
bovenliggende
commit
6ec66ce7db
22 gewijzigde bestanden met toevoegingen van 3591 en 1936 verwijderingen
  1. 7 7
      .gitignore
  2. 251 251
      doc/pockit.drawio
  3. 2 2
      doc/z-index.txt
  4. 1745 0
      src/composer-setup.php
  5. 12 12
      src/composer.json
  6. 537 537
      src/composer.lock
  7. BIN
      src/composer.phar
  8. 23 23
      src/config.php
  9. 1 1
      src/controllers/RegenController.php
  10. 36 36
      src/css/grades.css
  11. 8 8
      src/css/home.css
  12. 272 272
      src/css/pockit.css
  13. 194 194
      src/css/regen-report.css
  14. 39 39
      src/index.php
  15. 122 122
      src/js/pockit.js
  16. 113 113
      src/js/regenpreview.js
  17. 87 87
      src/pockit.php
  18. 2 2
      src/tools/database_up.php
  19. 48 48
      src/views/LayoutView.php
  20. 0 90
      src/views/RegenPageView.php
  21. 1 1
      start.sh
  22. 91 91
      tools/setup.php

+ 7 - 7
.gitignore

@@ -1,7 +1,7 @@
-pockit.geany
-src/vendor
-src/.env
-src/jquery
-src/jqueryui
-src/db.sqlite3
-src/img/regen/rgn*
+pockit.geany
+src/vendor
+src/.env
+src/jquery
+src/jqueryui
+src/db.sqlite3
+src/img/regen/rgn*

+ 251 - 251
doc/pockit.drawio

@@ -1,251 +1,251 @@
-<mxfile host="app.diagrams.net" modified="2023-12-14T18:43:50.696Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0" etag="JCQS5pvwg_oaY510Vpyx" version="22.1.9" type="device">
-  <diagram id="R2lEEEUBdFMjLlhIrx00" name="Page-1">
-    <mxGraphModel dx="733" dy="366" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0" extFonts="Permanent Marker^https://fonts.googleapis.com/css?family=Permanent+Marker">
-      <root>
-        <mxCell id="0" />
-        <mxCell id="1" parent="0" />
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-1" value="reports" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
-          <mxGeometry x="110" y="140" width="180" height="150" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-1">
-          <mxGeometry y="30" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-3" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-2">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-4" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-2">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-1">
-          <mxGeometry y="60" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-6" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-5">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-7" value="discipline_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-5">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-1">
-          <mxGeometry y="90" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-9" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-8">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-10" value="work_type_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-8">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-1">
-          <mxGeometry y="120" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-12" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-11">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-13" value="comment" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-11">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-14" value="disciplines" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
-          <mxGeometry x="450" y="120" width="180" height="120" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-15" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-14">
-          <mxGeometry y="30" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-16" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-15">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-17" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-15">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-18" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-14">
-          <mxGeometry y="60" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-19" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-18">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-20" value="name" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-18">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-21" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-14">
-          <mxGeometry y="90" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-22" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-21">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-23" value="teacher_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-21">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-27" value="teachers" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
-          <mxGeometry x="480" y="300" width="180" height="150" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-28" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-27">
-          <mxGeometry y="30" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-29" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-28">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-30" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-28">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-31" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-27">
-          <mxGeometry y="60" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-32" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-31">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-33" value="name" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-31">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-34" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-27">
-          <mxGeometry y="90" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-35" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-34">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-36" value="surname" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-34">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-37" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-27">
-          <mxGeometry y="120" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-38" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-37">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-39" value="patronymic" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-37">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-40" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;" edge="1" parent="1" source="yovVQwwNwtsDAoFcyGfq-28" target="yovVQwwNwtsDAoFcyGfq-21">
-          <mxGeometry width="100" height="100" relative="1" as="geometry">
-            <mxPoint x="230" y="420" as="sourcePoint" />
-            <mxPoint x="330" y="320" as="targetPoint" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-41" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;" edge="1" parent="1" source="yovVQwwNwtsDAoFcyGfq-15" target="yovVQwwNwtsDAoFcyGfq-5">
-          <mxGeometry width="100" height="100" relative="1" as="geometry">
-            <mxPoint x="300" y="250" as="sourcePoint" />
-            <mxPoint x="400" y="150" as="targetPoint" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-42" value="work_types" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
-          <mxGeometry x="160" y="340" width="180" height="90" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-43" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-42">
-          <mxGeometry y="30" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-44" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-43">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-45" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-43">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-46" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-42">
-          <mxGeometry y="60" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-47" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-46">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-48" value="name" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-46">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-55" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;" edge="1" parent="1" source="yovVQwwNwtsDAoFcyGfq-43" target="yovVQwwNwtsDAoFcyGfq-8">
-          <mxGeometry width="100" height="100" relative="1" as="geometry">
-            <mxPoint x="110" y="440" as="sourcePoint" />
-            <mxPoint x="210" y="340" as="targetPoint" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-56" value="settings" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
-          <mxGeometry x="160" y="490" width="180" height="120" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-57" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-56">
-          <mxGeometry y="30" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-58" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-57">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-59" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-57">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-60" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-56">
-          <mxGeometry y="60" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-61" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-60">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-62" value="name" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-60">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-63" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-56">
-          <mxGeometry y="90" width="180" height="30" as="geometry" />
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-64" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-63">
-          <mxGeometry width="30" height="30" as="geometry">
-            <mxRectangle width="30" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-        <mxCell id="yovVQwwNwtsDAoFcyGfq-65" value="value" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-63">
-          <mxGeometry x="30" width="150" height="30" as="geometry">
-            <mxRectangle width="150" height="30" as="alternateBounds" />
-          </mxGeometry>
-        </mxCell>
-      </root>
-    </mxGraphModel>
-  </diagram>
-</mxfile>
+<mxfile host="app.diagrams.net" modified="2023-12-14T18:43:50.696Z" agent="Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0" etag="JCQS5pvwg_oaY510Vpyx" version="22.1.9" type="device">
+  <diagram id="R2lEEEUBdFMjLlhIrx00" name="Page-1">
+    <mxGraphModel dx="733" dy="366" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0" extFonts="Permanent Marker^https://fonts.googleapis.com/css?family=Permanent+Marker">
+      <root>
+        <mxCell id="0" />
+        <mxCell id="1" parent="0" />
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-1" value="reports" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
+          <mxGeometry x="110" y="140" width="180" height="150" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-1">
+          <mxGeometry y="30" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-3" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-2">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-4" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-2">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-1">
+          <mxGeometry y="60" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-6" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-5">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-7" value="discipline_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-5">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-1">
+          <mxGeometry y="90" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-9" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-8">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-10" value="work_type_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-8">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-1">
+          <mxGeometry y="120" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-12" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-11">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-13" value="comment" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-11">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-14" value="disciplines" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
+          <mxGeometry x="450" y="120" width="180" height="120" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-15" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-14">
+          <mxGeometry y="30" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-16" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-15">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-17" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-15">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-18" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-14">
+          <mxGeometry y="60" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-19" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-18">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-20" value="name" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-18">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-21" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-14">
+          <mxGeometry y="90" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-22" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-21">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-23" value="teacher_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-21">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-27" value="teachers" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
+          <mxGeometry x="480" y="300" width="180" height="150" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-28" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-27">
+          <mxGeometry y="30" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-29" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-28">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-30" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-28">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-31" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-27">
+          <mxGeometry y="60" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-32" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-31">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-33" value="name" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-31">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-34" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-27">
+          <mxGeometry y="90" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-35" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-34">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-36" value="surname" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-34">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-37" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-27">
+          <mxGeometry y="120" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-38" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-37">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-39" value="patronymic" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-37">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-40" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;" edge="1" parent="1" source="yovVQwwNwtsDAoFcyGfq-28" target="yovVQwwNwtsDAoFcyGfq-21">
+          <mxGeometry width="100" height="100" relative="1" as="geometry">
+            <mxPoint x="230" y="420" as="sourcePoint" />
+            <mxPoint x="330" y="320" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-41" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;" edge="1" parent="1" source="yovVQwwNwtsDAoFcyGfq-15" target="yovVQwwNwtsDAoFcyGfq-5">
+          <mxGeometry width="100" height="100" relative="1" as="geometry">
+            <mxPoint x="300" y="250" as="sourcePoint" />
+            <mxPoint x="400" y="150" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-42" value="work_types" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
+          <mxGeometry x="160" y="340" width="180" height="90" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-43" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-42">
+          <mxGeometry y="30" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-44" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-43">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-45" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-43">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-46" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-42">
+          <mxGeometry y="60" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-47" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-46">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-48" value="name" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-46">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-55" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;" edge="1" parent="1" source="yovVQwwNwtsDAoFcyGfq-43" target="yovVQwwNwtsDAoFcyGfq-8">
+          <mxGeometry width="100" height="100" relative="1" as="geometry">
+            <mxPoint x="110" y="440" as="sourcePoint" />
+            <mxPoint x="210" y="340" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-56" value="settings" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="1">
+          <mxGeometry x="160" y="490" width="180" height="120" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-57" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-56">
+          <mxGeometry y="30" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-58" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-57">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-59" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-57">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-60" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-56">
+          <mxGeometry y="60" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-61" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-60">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-62" value="name" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-60">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-63" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-56">
+          <mxGeometry y="90" width="180" height="30" as="geometry" />
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-64" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-63">
+          <mxGeometry width="30" height="30" as="geometry">
+            <mxRectangle width="30" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="yovVQwwNwtsDAoFcyGfq-65" value="value" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="yovVQwwNwtsDAoFcyGfq-63">
+          <mxGeometry x="30" width="150" height="30" as="geometry">
+            <mxRectangle width="150" height="30" as="alternateBounds" />
+          </mxGeometry>
+        </mxCell>
+      </root>
+    </mxGraphModel>
+  </diagram>
+</mxfile>

+ 2 - 2
doc/z-index.txt

@@ -1,3 +1,3 @@
-0: body
-1: .card
+0: body
+1: .card
 2: .modal

+ 1745 - 0
src/composer-setup.php

@@ -0,0 +1,1745 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+setupEnvironment();
+process(is_array($argv) ? $argv : array());
+
+/**
+ * Initializes various values
+ *
+ * @throws RuntimeException If uopz extension prevents exit calls
+ */
+function setupEnvironment()
+{
+    ini_set('display_errors', 1);
+
+    if (extension_loaded('uopz') && !(ini_get('uopz.disable') || ini_get('uopz.exit'))) {
+        // uopz works at opcode level and disables exit calls
+        if (function_exists('uopz_allow_exit')) {
+            @uopz_allow_exit(true);
+        } else {
+            throw new RuntimeException('The uopz extension ignores exit calls and breaks this installer.');
+        }
+    }
+
+    $installer = 'ComposerInstaller';
+
+    if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
+        if ($version = getenv('COMPOSERSETUP')) {
+            $installer = sprintf('Composer-Setup.exe/%s', $version);
+        }
+    }
+
+    define('COMPOSER_INSTALLER', $installer);
+}
+
+/**
+ * Processes the installer
+ */
+function process($argv)
+{
+    // Determine ANSI output from --ansi and --no-ansi flags
+    setUseAnsi($argv);
+
+    $help = in_array('--help', $argv) || in_array('-h', $argv);
+    if ($help) {
+        displayHelp();
+        exit(0);
+    }
+
+    $check      = in_array('--check', $argv);
+    $force      = in_array('--force', $argv);
+    $quiet      = in_array('--quiet', $argv);
+    $channel    = 'stable';
+    if (in_array('--snapshot', $argv)) {
+        $channel = 'snapshot';
+    } elseif (in_array('--preview', $argv)) {
+        $channel = 'preview';
+    } elseif (in_array('--1', $argv)) {
+        $channel = '1';
+    } elseif (in_array('--2', $argv)) {
+        $channel = '2';
+    } elseif (in_array('--2.2', $argv)) {
+        $channel = '2.2';
+    }
+    $disableTls = in_array('--disable-tls', $argv);
+    $installDir = getOptValue('--install-dir', $argv, false);
+    $version    = getOptValue('--version', $argv, false);
+    $filename   = getOptValue('--filename', $argv, 'composer.phar');
+    $cafile     = getOptValue('--cafile', $argv, false);
+
+    if (!checkParams($installDir, $version, $cafile)) {
+        exit(1);
+    }
+
+    $ok = checkPlatform($warnings, $quiet, $disableTls, true);
+
+    if ($check) {
+        // Only show warnings if we haven't output any errors
+        if ($ok) {
+            showWarnings($warnings);
+            showSecurityWarning($disableTls);
+        }
+        exit($ok ? 0 : 1);
+    }
+
+    if ($ok || $force) {
+        if ($channel === '1' && !$quiet) {
+            out('Warning: You forced the install of Composer 1.x via --1, but Composer 2.x is the latest stable version. Updating to it via composer self-update --stable is recommended.', 'error');
+        }
+
+        $installer = new Installer($quiet, $disableTls, $cafile);
+        if ($installer->run($version, $installDir, $filename, $channel)) {
+            showWarnings($warnings);
+            showSecurityWarning($disableTls);
+            exit(0);
+        }
+    }
+
+    exit(1);
+}
+
+/**
+ * Displays the help
+ */
+function displayHelp()
+{
+    echo <<<EOF
+Composer Installer
+------------------
+Options
+--help               this help
+--check              for checking environment only
+--force              forces the installation
+--ansi               force ANSI color output
+--no-ansi            disable ANSI color output
+--quiet              do not output unimportant messages
+--install-dir="..."  accepts a target installation directory
+--preview            install the latest version from the preview (alpha/beta/rc) channel instead of stable
+--snapshot           install the latest version from the snapshot (dev builds) channel instead of stable
+--1                  install the latest stable Composer 1.x (EOL) version
+--2                  install the latest stable Composer 2.x version
+--2.2                install the latest stable Composer 2.2.x (LTS) version
+--version="..."      accepts a specific version to install instead of the latest
+--filename="..."     accepts a target filename (default: composer.phar)
+--disable-tls        disable SSL/TLS security for file downloads
+--cafile="..."       accepts a path to a Certificate Authority (CA) certificate file for SSL/TLS verification
+
+EOF;
+}
+
+/**
+ * Sets the USE_ANSI define for colorizing output
+ *
+ * @param array $argv Command-line arguments
+ */
+function setUseAnsi($argv)
+{
+    // --no-ansi wins over --ansi
+    if (in_array('--no-ansi', $argv)) {
+        define('USE_ANSI', false);
+    } elseif (in_array('--ansi', $argv)) {
+        define('USE_ANSI', true);
+    } else {
+        define('USE_ANSI', outputSupportsColor());
+    }
+}
+
+/**
+ * Returns whether color output is supported
+ *
+ * @return bool
+ */
+function outputSupportsColor()
+{
+    if (false !== getenv('NO_COLOR') || !defined('STDOUT')) {
+        return false;
+    }
+
+    if ('Hyper' === getenv('TERM_PROGRAM')) {
+        return true;
+    }
+
+    if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+        return (function_exists('sapi_windows_vt100_support')
+            && sapi_windows_vt100_support(STDOUT))
+            || false !== getenv('ANSICON')
+            || 'ON' === getenv('ConEmuANSI')
+            || 'xterm' === getenv('TERM');
+    }
+
+    if (function_exists('stream_isatty')) {
+        return stream_isatty(STDOUT);
+    }
+
+    if (function_exists('posix_isatty')) {
+        return posix_isatty(STDOUT);
+    }
+
+    $stat = fstat(STDOUT);
+    // Check if formatted mode is S_IFCHR
+    return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
+}
+
+/**
+ * Returns the value of a command-line option
+ *
+ * @param string $opt The command-line option to check
+ * @param array $argv Command-line arguments
+ * @param mixed $default Default value to be returned
+ *
+ * @return mixed The command-line value or the default
+ */
+function getOptValue($opt, $argv, $default)
+{
+    $optLength = strlen($opt);
+
+    foreach ($argv as $key => $value) {
+        $next = $key + 1;
+        if (0 === strpos($value, $opt)) {
+            if ($optLength === strlen($value) && isset($argv[$next])) {
+                return trim($argv[$next]);
+            } else {
+                return trim(substr($value, $optLength + 1));
+            }
+        }
+    }
+
+    return $default;
+}
+
+/**
+ * Checks that user-supplied params are valid
+ *
+ * @param mixed $installDir The required istallation directory
+ * @param mixed $version The required composer version to install
+ * @param mixed $cafile Certificate Authority file
+ *
+ * @return bool True if the supplied params are okay
+ */
+function checkParams($installDir, $version, $cafile)
+{
+    $result = true;
+
+    if (false !== $installDir && !is_dir($installDir)) {
+        out("The defined install dir ({$installDir}) does not exist.", 'info');
+        $result = false;
+    }
+
+    if (false !== $version && 1 !== preg_match('/^\d+\.\d+\.\d+(\-(alpha|beta|RC)\d*)*$/', $version)) {
+        out("The defined install version ({$version}) does not match release pattern.", 'info');
+        $result = false;
+    }
+
+    if (false !== $cafile && (!file_exists($cafile) || !is_readable($cafile))) {
+        out("The defined Certificate Authority (CA) cert file ({$cafile}) does not exist or is not readable.", 'info');
+        $result = false;
+    }
+    return $result;
+}
+
+/**
+ * Checks the platform for possible issues running Composer
+ *
+ * Errors are written to the output, warnings are saved for later display.
+ *
+ * @param array $warnings Populated by method, to be shown later
+ * @param bool $quiet Quiet mode
+ * @param bool $disableTls Bypass tls
+ * @param bool $install If we are installing, rather than diagnosing
+ *
+ * @return bool True if there are no errors
+ */
+function checkPlatform(&$warnings, $quiet, $disableTls, $install)
+{
+    getPlatformIssues($errors, $warnings, $install);
+
+    // Make openssl warning an error if tls has not been specifically disabled
+    if (isset($warnings['openssl']) && !$disableTls) {
+        $errors['openssl'] = $warnings['openssl'];
+        unset($warnings['openssl']);
+    }
+
+    if (!empty($errors)) {
+        // Composer-Setup.exe uses "Some settings" to flag platform errors
+        out('Some settings on your machine make Composer unable to work properly.', 'error');
+        out('Make sure that you fix the issues listed below and run this script again:', 'error');
+        outputIssues($errors);
+        return false;
+    }
+
+    if (empty($warnings) && !$quiet) {
+        out('All settings correct for using Composer', 'success');
+    }
+    return true;
+}
+
+/**
+ * Checks platform configuration for common incompatibility issues
+ *
+ * @param array $errors Populated by method
+ * @param array $warnings Populated by method
+ * @param bool $install If we are installing, rather than diagnosing
+ *
+ * @return bool If any errors or warnings have been found
+ */
+function getPlatformIssues(&$errors, &$warnings, $install)
+{
+    $errors = array();
+    $warnings = array();
+
+    if ($iniPath = php_ini_loaded_file()) {
+        $iniMessage = PHP_EOL.'The php.ini used by your command-line PHP is: ' . $iniPath;
+    } else {
+        $iniMessage = PHP_EOL.'A php.ini file does not exist. You will have to create one.';
+    }
+    $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.';
+
+    if (ini_get('detect_unicode')) {
+        $errors['unicode'] = array(
+            'The detect_unicode setting must be disabled.',
+            'Add the following to the end of your `php.ini`:',
+            '    detect_unicode = Off',
+            $iniMessage
+        );
+    }
+
+    if (extension_loaded('suhosin')) {
+        $suhosin = ini_get('suhosin.executor.include.whitelist');
+        $suhosinBlacklist = ini_get('suhosin.executor.include.blacklist');
+        if (false === stripos($suhosin, 'phar') && (!$suhosinBlacklist || false !== stripos($suhosinBlacklist, 'phar'))) {
+            $errors['suhosin'] = array(
+                'The suhosin.executor.include.whitelist setting is incorrect.',
+                'Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):',
+                '    suhosin.executor.include.whitelist = phar '.$suhosin,
+                $iniMessage
+            );
+        }
+    }
+
+    if (!function_exists('json_decode')) {
+        $errors['json'] = array(
+            'The json extension is missing.',
+            'Install it or recompile php without --disable-json'
+        );
+    }
+
+    if (!extension_loaded('Phar')) {
+        $errors['phar'] = array(
+            'The phar extension is missing.',
+            'Install it or recompile php without --disable-phar'
+        );
+    }
+
+    if (!extension_loaded('filter')) {
+        $errors['filter'] = array(
+            'The filter extension is missing.',
+            'Install it or recompile php without --disable-filter'
+        );
+    }
+
+    if (!extension_loaded('hash')) {
+        $errors['hash'] = array(
+            'The hash extension is missing.',
+            'Install it or recompile php without --disable-hash'
+        );
+    }
+
+    if (!extension_loaded('iconv') && !extension_loaded('mbstring')) {
+        $errors['iconv_mbstring'] = array(
+            'The iconv OR mbstring extension is required and both are missing.',
+            'Install either of them or recompile php without --disable-iconv'
+        );
+    }
+
+    if (!ini_get('allow_url_fopen')) {
+        $errors['allow_url_fopen'] = array(
+            'The allow_url_fopen setting is incorrect.',
+            'Add the following to the end of your `php.ini`:',
+            '    allow_url_fopen = On',
+            $iniMessage
+        );
+    }
+
+    if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) {
+        $ioncube = ioncube_loader_version();
+        $errors['ioncube'] = array(
+            'Your ionCube Loader extension ('.$ioncube.') is incompatible with Phar files.',
+            'Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:',
+            '    zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so',
+            $iniMessage
+        );
+    }
+
+    if (version_compare(PHP_VERSION, '5.3.2', '<')) {
+        $errors['php'] = array(
+            'Your PHP ('.PHP_VERSION.') is too old, you must upgrade to PHP 5.3.2 or higher.'
+        );
+    }
+
+    if (version_compare(PHP_VERSION, '5.3.4', '<')) {
+        $warnings['php'] = array(
+            'Your PHP ('.PHP_VERSION.') is quite old, upgrading to PHP 5.3.4 or higher is recommended.',
+            'Composer works with 5.3.2+ for most people, but there might be edge case issues.'
+        );
+    }
+
+    if (!extension_loaded('openssl')) {
+        $warnings['openssl'] = array(
+            'The openssl extension is missing, which means that secure HTTPS transfers are impossible.',
+            'If possible you should enable it or recompile php with --with-openssl'
+        );
+    }
+
+    if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) {
+        // Attempt to parse version number out, fallback to whole string value.
+        $opensslVersion = trim(strstr(OPENSSL_VERSION_TEXT, ' '));
+        $opensslVersion = substr($opensslVersion, 0, strpos($opensslVersion, ' '));
+        $opensslVersion = $opensslVersion ? $opensslVersion : OPENSSL_VERSION_TEXT;
+
+        $warnings['openssl_version'] = array(
+            'The OpenSSL library ('.$opensslVersion.') used by PHP does not support TLSv1.2 or TLSv1.1.',
+            'If possible you should upgrade OpenSSL to version 1.0.1 or above.'
+        );
+    }
+
+    if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && ini_get('apc.enable_cli')) {
+        $warnings['apc_cli'] = array(
+            'The apc.enable_cli setting is incorrect.',
+            'Add the following to the end of your `php.ini`:',
+            '    apc.enable_cli = Off',
+            $iniMessage
+        );
+    }
+
+    if (!$install && extension_loaded('xdebug')) {
+        $warnings['xdebug_loaded'] = array(
+            'The xdebug extension is loaded, this can slow down Composer a little.',
+            'Disabling it when using Composer is recommended.'
+        );
+
+        if (ini_get('xdebug.profiler_enabled')) {
+            $warnings['xdebug_profile'] = array(
+                'The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.',
+                'Add the following to the end of your `php.ini` to disable it:',
+                '    xdebug.profiler_enabled = 0',
+                $iniMessage
+            );
+        }
+    }
+
+    if (!extension_loaded('zlib')) {
+        $warnings['zlib'] = array(
+            'The zlib extension is not loaded, this can slow down Composer a lot.',
+            'If possible, install it or recompile php with --with-zlib',
+            $iniMessage
+        );
+    }
+
+    if (defined('PHP_WINDOWS_VERSION_BUILD')
+        && (version_compare(PHP_VERSION, '7.2.23', '<')
+        || (version_compare(PHP_VERSION, '7.3.0', '>=')
+        && version_compare(PHP_VERSION, '7.3.10', '<')))) {
+        $warnings['onedrive'] = array(
+            'The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10.',
+            'Upgrade your PHP ('.PHP_VERSION.') to use this location with Composer.'
+        );
+    }
+
+    if (extension_loaded('uopz') && !(ini_get('uopz.disable') || ini_get('uopz.exit'))) {
+        $warnings['uopz'] = array(
+            'The uopz extension ignores exit calls and may not work with all Composer commands.',
+            'Disabling it when using Composer is recommended.'
+        );
+    }
+
+    ob_start();
+    phpinfo(INFO_GENERAL);
+    $phpinfo = ob_get_clean();
+    if (preg_match('{Configure Command(?: *</td><td class="v">| *=> *)(.*?)(?:</td>|$)}m', $phpinfo, $match)) {
+        $configure = $match[1];
+
+        if (false !== strpos($configure, '--enable-sigchild')) {
+            $warnings['sigchild'] = array(
+                'PHP was compiled with --enable-sigchild which can cause issues on some platforms.',
+                'Recompile it without this flag if possible, see also:',
+                '    https://bugs.php.net/bug.php?id=22999'
+            );
+        }
+
+        if (false !== strpos($configure, '--with-curlwrappers')) {
+            $warnings['curlwrappers'] = array(
+                'PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.',
+                'Recompile it without this flag if possible'
+            );
+        }
+    }
+
+    // Stringify the message arrays
+    foreach ($errors as $key => $value) {
+        $errors[$key] = PHP_EOL.implode(PHP_EOL, $value);
+    }
+
+    foreach ($warnings as $key => $value) {
+        $warnings[$key] = PHP_EOL.implode(PHP_EOL, $value);
+    }
+
+    return !empty($errors) || !empty($warnings);
+}
+
+
+/**
+ * Outputs an array of issues
+ *
+ * @param array $issues
+ */
+function outputIssues($issues)
+{
+    foreach ($issues as $issue) {
+        out($issue, 'info');
+    }
+    out('');
+}
+
+/**
+ * Outputs any warnings found
+ *
+ * @param array $warnings
+ */
+function showWarnings($warnings)
+{
+    if (!empty($warnings)) {
+        out('Some settings on your machine may cause stability issues with Composer.', 'error');
+        out('If you encounter issues, try to change the following:', 'error');
+        outputIssues($warnings);
+    }
+}
+
+/**
+ * Outputs an end of process warning if tls has been bypassed
+ *
+ * @param bool $disableTls Bypass tls
+ */
+function showSecurityWarning($disableTls)
+{
+    if ($disableTls) {
+        out('You have instructed the Installer not to enforce SSL/TLS security on remote HTTPS requests.', 'info');
+        out('This will leave all downloads during installation vulnerable to Man-In-The-Middle (MITM) attacks', 'info');
+    }
+}
+
+/**
+ * colorize output
+ */
+function out($text, $color = null, $newLine = true)
+{
+    $styles = array(
+        'success' => "\033[0;32m%s\033[0m",
+        'error' => "\033[31;31m%s\033[0m",
+        'info' => "\033[33;33m%s\033[0m"
+    );
+
+    $format = '%s';
+
+    if (isset($styles[$color]) && USE_ANSI) {
+        $format = $styles[$color];
+    }
+
+    if ($newLine) {
+        $format .= PHP_EOL;
+    }
+
+    printf($format, $text);
+}
+
+/**
+ * Returns the system-dependent Composer home location, which may not exist
+ *
+ * @return string
+ */
+function getHomeDir()
+{
+    $home = getenv('COMPOSER_HOME');
+    if ($home) {
+        return $home;
+    }
+
+    $userDir = getUserDir();
+
+    if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
+        return $userDir.'/Composer';
+    }
+
+    $dirs = array();
+
+    if (useXdg()) {
+        // XDG Base Directory Specifications
+        $xdgConfig = getenv('XDG_CONFIG_HOME');
+        if (!$xdgConfig) {
+            $xdgConfig = $userDir . '/.config';
+        }
+
+        $dirs[] = $xdgConfig . '/composer';
+    }
+
+    $dirs[] = $userDir . '/.composer';
+
+    // select first dir which exists of: $XDG_CONFIG_HOME/composer or ~/.composer
+    foreach ($dirs as $dir) {
+        if (is_dir($dir)) {
+            return $dir;
+        }
+    }
+
+    // if none exists, we default to first defined one (XDG one if system uses it, or ~/.composer otherwise)
+    return $dirs[0];
+}
+
+/**
+ * Returns the location of the user directory from the environment
+ * @throws RuntimeException If the environment value does not exists
+ *
+ * @return string
+ */
+function getUserDir()
+{
+    $userEnv = defined('PHP_WINDOWS_VERSION_MAJOR') ? 'APPDATA' : 'HOME';
+    $userDir = getenv($userEnv);
+
+    if (!$userDir) {
+        throw new RuntimeException('The '.$userEnv.' or COMPOSER_HOME environment variable must be set for composer to run correctly');
+    }
+
+    return rtrim(strtr($userDir, '\\', '/'), '/');
+}
+
+/**
+ * @return bool
+ */
+function useXdg()
+{
+    foreach (array_keys($_SERVER) as $key) {
+        if (strpos($key, 'XDG_') === 0) {
+            return true;
+        }
+    }
+
+    if (is_dir('/etc/xdg')) {
+        return true;
+    }
+
+    return false;
+}
+
+function validateCaFile($contents)
+{
+    // assume the CA is valid if php is vulnerable to
+    // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
+    if (
+        PHP_VERSION_ID <= 50327
+        || (PHP_VERSION_ID >= 50400 && PHP_VERSION_ID < 50422)
+        || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50506)
+    ) {
+        return !empty($contents);
+    }
+
+    return (bool) openssl_x509_parse($contents);
+}
+
+class Installer
+{
+    private $quiet;
+    private $disableTls;
+    private $cafile;
+    private $displayPath;
+    private $target;
+    private $tmpFile;
+    private $tmpCafile;
+    private $baseUrl;
+    private $algo;
+    private $errHandler;
+    private $httpClient;
+    private $pubKeys = array();
+    private $installs = array();
+
+    /**
+     * Constructor - must not do anything that throws an exception
+     *
+     * @param bool $quiet Quiet mode
+     * @param bool $disableTls Bypass tls
+     * @param mixed $cafile Path to CA bundle, or false
+     */
+    public function __construct($quiet, $disableTls, $caFile)
+    {
+        if (($this->quiet = $quiet)) {
+            ob_start();
+        }
+        $this->disableTls = $disableTls;
+        $this->cafile = $caFile;
+        $this->errHandler = new ErrorHandler();
+    }
+
+    /**
+     * Runs the installer
+     *
+     * @param mixed $version Specific version to install, or false
+     * @param mixed $installDir Specific installation directory, or false
+     * @param string $filename Specific filename to save to, or composer.phar
+     * @param string $channel Specific version channel to use
+     * @throws Exception If anything other than a RuntimeException is caught
+     *
+     * @return bool If the installation succeeded
+     */
+    public function run($version, $installDir, $filename, $channel)
+    {
+        try {
+            $this->initTargets($installDir, $filename);
+            $this->initTls();
+            $this->httpClient = new HttpClient($this->disableTls, $this->cafile);
+            $result = $this->install($version, $channel);
+
+            // in case --1 or --2 is passed, we leave the default channel for next self-update to stable
+            if (1 === preg_match('{^\d+$}D', $channel)) {
+                $channel = 'stable';
+            }
+
+            if ($result && $channel !== 'stable' && !$version && defined('PHP_BINARY')) {
+                $null = (defined('PHP_WINDOWS_VERSION_MAJOR') ? 'NUL' : '/dev/null');
+                @exec(escapeshellarg(PHP_BINARY) .' '.escapeshellarg($this->target).' self-update --'.$channel.' --set-channel-only -q > '.$null.' 2> '.$null, $output);
+            }
+        } catch (Exception $e) {
+            $result = false;
+        }
+
+        // Always clean up
+        $this->cleanUp($result);
+
+        if (isset($e)) {
+            // Rethrow anything that is not a RuntimeException
+            if (!$e instanceof RuntimeException) {
+                throw $e;
+            }
+            out($e->getMessage(), 'error');
+        }
+        return $result;
+    }
+
+    /**
+     * Initialization methods to set the required filenames and composer url
+     *
+     * @param mixed $installDir Specific installation directory, or false
+     * @param string $filename Specific filename to save to, or composer.phar
+     * @throws RuntimeException If the installation directory is not writable
+     */
+    protected function initTargets($installDir, $filename)
+    {
+        $this->displayPath = ($installDir ? rtrim($installDir, '/').'/' : '').$filename;
+        $installDir = $installDir ? realpath($installDir) : getcwd();
+
+        if (!is_writeable($installDir)) {
+            throw new RuntimeException('The installation directory "'.$installDir.'" is not writable');
+        }
+
+        $this->target = $installDir.DIRECTORY_SEPARATOR.$filename;
+        $this->tmpFile = $installDir.DIRECTORY_SEPARATOR.basename($this->target, '.phar').'-temp.phar';
+
+        $uriScheme = $this->disableTls ? 'http' : 'https';
+        $this->baseUrl = $uriScheme.'://getcomposer.org';
+    }
+
+    /**
+     * A wrapper around methods to check tls and write public keys
+     * @throws RuntimeException If SHA384 is not supported
+     */
+    protected function initTls()
+    {
+        if ($this->disableTls) {
+            return;
+        }
+
+        if (!in_array('sha384', array_map('strtolower', openssl_get_md_methods()))) {
+            throw new RuntimeException('SHA384 is not supported by your openssl extension');
+        }
+
+        $this->algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384';
+        $home = $this->getComposerHome();
+
+        $this->pubKeys = array(
+            'dev' => $this->installKey(self::getPKDev(), $home, 'keys.dev.pub'),
+            'tags' => $this->installKey(self::getPKTags(), $home, 'keys.tags.pub')
+        );
+
+        if (empty($this->cafile) && !HttpClient::getSystemCaRootBundlePath()) {
+            $this->cafile = $this->tmpCafile = $this->installKey(HttpClient::getPackagedCaFile(), $home, 'cacert-temp.pem');
+        }
+    }
+
+    /**
+     * Returns the Composer home directory, creating it if required
+     * @throws RuntimeException If the directory cannot be created
+     *
+     * @return string
+     */
+    protected function getComposerHome()
+    {
+        $home = getHomeDir();
+
+        if (!is_dir($home)) {
+            $this->errHandler->start();
+
+            if (!mkdir($home, 0777, true)) {
+                throw new RuntimeException(sprintf(
+                    'Unable to create Composer home directory "%s": %s',
+                    $home,
+                    $this->errHandler->message
+                ));
+            }
+            $this->installs[] = $home;
+            $this->errHandler->stop();
+        }
+        return $home;
+    }
+
+    /**
+     * Writes public key data to disc
+     *
+     * @param string $data The public key(s) in pem format
+     * @param string $path The directory to write to
+     * @param string $filename The name of the file
+     * @throws RuntimeException If the file cannot be written
+     *
+     * @return string The path to the saved data
+     */
+    protected function installKey($data, $path, $filename)
+    {
+        $this->errHandler->start();
+
+        $target = $path.DIRECTORY_SEPARATOR.$filename;
+        $installed = file_exists($target);
+        $write = file_put_contents($target, $data, LOCK_EX);
+        @chmod($target, 0644);
+
+        $this->errHandler->stop();
+
+        if (!$write) {
+            throw new RuntimeException(sprintf('Unable to write %s to: %s', $filename, $path));
+        }
+
+        if (!$installed) {
+            $this->installs[] = $target;
+        }
+
+        return $target;
+    }
+
+    /**
+     * The main install function
+     *
+     * @param mixed $version Specific version to install, or false
+     * @param string $channel Version channel to use
+     *
+     * @return bool If the installation succeeded
+     */
+    protected function install($version, $channel)
+    {
+        $retries = 3;
+        $result = false;
+        $infoMsg = 'Downloading...';
+        $infoType = 'info';
+
+        while ($retries--) {
+            if (!$this->quiet) {
+                out($infoMsg, $infoType);
+                $infoMsg = 'Retrying...';
+                $infoType = 'error';
+            }
+
+            if (!$this->getVersion($channel, $version, $url, $error)) {
+                out($error, 'error');
+                continue;
+            }
+
+            if (!$this->downloadToTmp($url, $signature, $error)) {
+                out($error, 'error');
+                continue;
+            }
+
+            if (!$this->verifyAndSave($version, $signature, $error)) {
+                out($error, 'error');
+                continue;
+            }
+
+            $result = true;
+            break;
+        }
+
+        if (!$this->quiet) {
+            if ($result) {
+                out(PHP_EOL."Composer (version {$version}) successfully installed to: {$this->target}", 'success');
+                out("Use it: php {$this->displayPath}", 'info');
+                out('');
+            } else {
+                out('The download failed repeatedly, aborting.', 'error');
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * Sets the version url, downloading version data if required
+     *
+     * @param string $channel Version channel to use
+     * @param false|string $version Version to install, or set by method
+     * @param null|string $url The versioned url, set by method
+     * @param null|string $error Set by method on failure
+     *
+     * @return bool If the operation succeeded
+     */
+    protected function getVersion($channel, &$version, &$url, &$error)
+    {
+        $error = '';
+
+        if ($version) {
+            if (empty($url)) {
+                $url = $this->baseUrl."/download/{$version}/composer.phar";
+            }
+            return true;
+        }
+
+        $this->errHandler->start();
+
+        if ($this->downloadVersionData($data, $error)) {
+            $this->parseVersionData($data, $channel, $version, $url);
+        }
+
+        $this->errHandler->stop();
+        return empty($error);
+    }
+
+    /**
+     * Downloads and json-decodes version data
+     *
+     * @param null|array $data Downloaded version data, set by method
+     * @param null|string $error Set by method on failure
+     *
+     * @return bool If the operation succeeded
+     */
+    protected function downloadVersionData(&$data, &$error)
+    {
+        $url = $this->baseUrl.'/versions';
+        $errFmt = 'The "%s" file could not be %s: %s';
+
+        if (!$json = $this->httpClient->get($url)) {
+            $error = sprintf($errFmt, $url, 'downloaded', $this->errHandler->message);
+            return false;
+        }
+
+        if (!$data = json_decode($json, true)) {
+            $error = sprintf($errFmt, $url, 'json-decoded', $this->getJsonError());
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * A wrapper around the methods needed to download and save the phar
+     *
+     * @param string $url The versioned download url
+     * @param null|string $signature Set by method on successful download
+     * @param null|string $error Set by method on failure
+     *
+     * @return bool If the operation succeeded
+     */
+    protected function downloadToTmp($url, &$signature, &$error)
+    {
+        $error = '';
+        $errFmt = 'The "%s" file could not be downloaded: %s';
+        $sigUrl = $url.'.sig';
+        $this->errHandler->start();
+
+        if (!$fh = fopen($this->tmpFile, 'w')) {
+            $error = sprintf('Could not create file "%s": %s', $this->tmpFile, $this->errHandler->message);
+
+        } elseif (!$this->getSignature($sigUrl, $signature)) {
+            $error = sprintf($errFmt, $sigUrl, $this->errHandler->message);
+
+        } elseif (!fwrite($fh, $this->httpClient->get($url))) {
+            $error = sprintf($errFmt, $url, $this->errHandler->message);
+        }
+
+        if (is_resource($fh)) {
+            fclose($fh);
+        }
+        $this->errHandler->stop();
+        return empty($error);
+    }
+
+    /**
+     * Verifies the downloaded file and saves it to the target location
+     *
+     * @param string $version The composer version downloaded
+     * @param string $signature The digital signature to check
+     * @param null|string $error Set by method on failure
+     *
+     * @return bool If the operation succeeded
+     */
+    protected function verifyAndSave($version, $signature, &$error)
+    {
+        $error = '';
+
+        if (!$this->validatePhar($this->tmpFile, $pharError)) {
+            $error = 'The download is corrupt: '.$pharError;
+
+        } elseif (!$this->verifySignature($version, $signature, $this->tmpFile)) {
+            $error = 'Signature mismatch, could not verify the phar file integrity';
+
+        } else {
+            $this->errHandler->start();
+
+            if (!rename($this->tmpFile, $this->target)) {
+                $error = sprintf('Could not write to file "%s": %s', $this->target, $this->errHandler->message);
+            }
+            chmod($this->target, 0755);
+            $this->errHandler->stop();
+        }
+
+        return empty($error);
+    }
+
+    /**
+     * Parses an array of version data to match the required channel
+     *
+     * @param array $data Downloaded version data
+     * @param mixed $channel Version channel to use
+     * @param false|string $version Set by method
+     * @param mixed $url The versioned url, set by method
+     */
+    protected function parseVersionData(array $data, $channel, &$version, &$url)
+    {
+        foreach ($data[$channel] as $candidate) {
+            if ($candidate['min-php'] <= PHP_VERSION_ID) {
+                $version = $candidate['version'];
+                $url = $this->baseUrl.$candidate['path'];
+                break;
+            }
+        }
+
+        if (!$version) {
+            $error = sprintf(
+                'None of the %d %s version(s) of Composer matches your PHP version (%s / ID: %d)',
+                count($data[$channel]),
+                $channel,
+                PHP_VERSION,
+                PHP_VERSION_ID
+            );
+            throw new RuntimeException($error);
+        }
+    }
+
+    /**
+     * Downloads the digital signature of required phar file
+     *
+     * @param string $url The signature url
+     * @param null|string $signature Set by method on success
+     *
+     * @return bool If the download succeeded
+     */
+    protected function getSignature($url, &$signature)
+    {
+        if (!$result = $this->disableTls) {
+            $signature = $this->httpClient->get($url);
+
+            if ($signature) {
+                $signature = json_decode($signature, true);
+                $signature = base64_decode($signature['sha384']);
+                $result = true;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Verifies the signature of the downloaded phar
+     *
+     * @param string $version The composer versione
+     * @param string $signature The downloaded digital signature
+     * @param string $file The temp phar file
+     *
+     * @return bool If the operation succeeded
+     */
+    protected function verifySignature($version, $signature, $file)
+    {
+        if (!$result = $this->disableTls) {
+            $path = preg_match('{^[0-9a-f]{40}$}', $version) ? $this->pubKeys['dev'] : $this->pubKeys['tags'];
+            $pubkeyid = openssl_pkey_get_public('file://'.$path);
+
+            $result = 1 === openssl_verify(
+                file_get_contents($file),
+                $signature,
+                $pubkeyid,
+                $this->algo
+            );
+
+            // PHP 8 automatically frees the key instance and deprecates the function
+            if (PHP_VERSION_ID < 80000) {
+                openssl_free_key($pubkeyid);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Validates the downloaded phar file
+     *
+     * @param string $pharFile The temp phar file
+     * @param null|string $error Set by method on failure
+     *
+     * @return bool If the operation succeeded
+     */
+    protected function validatePhar($pharFile, &$error)
+    {
+        if (ini_get('phar.readonly')) {
+            return true;
+        }
+
+        try {
+            // Test the phar validity
+            $phar = new Phar($pharFile);
+            // Free the variable to unlock the file
+            unset($phar);
+            $result = true;
+
+        } catch (Exception $e) {
+            if (!$e instanceof UnexpectedValueException && !$e instanceof PharException) {
+                throw $e;
+            }
+            $error = $e->getMessage();
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * Returns a string representation of the last json error
+     *
+     * @return string The error string or code
+     */
+    protected function getJsonError()
+    {
+        if (function_exists('json_last_error_msg')) {
+            return json_last_error_msg();
+        } else {
+            return 'json_last_error = '.json_last_error();
+        }
+    }
+
+    /**
+     * Cleans up resources at the end of the installation
+     *
+     * @param bool $result If the installation succeeded
+     */
+    protected function cleanUp($result)
+    {
+        if (!$result) {
+            // Output buffered errors
+            if ($this->quiet) {
+                $this->outputErrors();
+            }
+            // Clean up stuff we created
+            $this->uninstall();
+        } elseif ($this->tmpCafile) {
+            @unlink($this->tmpCafile);
+        }
+    }
+
+    /**
+     * Outputs unique errors when in quiet mode
+     *
+     */
+    protected function outputErrors()
+    {
+        $errors = explode(PHP_EOL, ob_get_clean());
+        $shown = array();
+
+        foreach ($errors as $error) {
+            if ($error && !in_array($error, $shown)) {
+                out($error, 'error');
+                $shown[] = $error;
+            }
+        }
+    }
+
+    /**
+     * Uninstalls newly-created files and directories on failure
+     *
+     */
+    protected function uninstall()
+    {
+        foreach (array_reverse($this->installs) as $target) {
+            if (is_file($target)) {
+                @unlink($target);
+            } elseif (is_dir($target)) {
+                @rmdir($target);
+            }
+        }
+
+        if ($this->tmpFile !== null && file_exists($this->tmpFile)) {
+            @unlink($this->tmpFile);
+        }
+    }
+
+    public static function getPKDev()
+    {
+        return <<<PKDEV
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnBDHjZS6e0ZMoK3xTD7f
+FNCzlXjX/Aie2dit8QXA03pSrOTbaMnxON3hUL47Lz3g1SC6YJEMVHr0zYq4elWi
+i3ecFEgzLcj+pZM5X6qWu2Ozz4vWx3JYo1/a/HYdOuW9e3lwS8VtS0AVJA+U8X0A
+hZnBmGpltHhO8hPKHgkJtkTUxCheTcbqn4wGHl8Z2SediDcPTLwqezWKUfrYzu1f
+o/j3WFwFs6GtK4wdYtiXr+yspBZHO3y1udf8eFFGcb2V3EaLOrtfur6XQVizjOuk
+8lw5zzse1Qp/klHqbDRsjSzJ6iL6F4aynBc6Euqt/8ccNAIz0rLjLhOraeyj4eNn
+8iokwMKiXpcrQLTKH+RH1JCuOVxQ436bJwbSsp1VwiqftPQieN+tzqy+EiHJJmGf
+TBAbWcncicCk9q2md+AmhNbvHO4PWbbz9TzC7HJb460jyWeuMEvw3gNIpEo2jYa9
+pMV6cVqnSa+wOc0D7pC9a6bne0bvLcm3S+w6I5iDB3lZsb3A9UtRiSP7aGSo7D72
+8tC8+cIgZcI7k9vjvOqH+d7sdOU2yPCnRY6wFh62/g8bDnUpr56nZN1G89GwM4d4
+r/TU7BQQIzsZgAiqOGXvVklIgAMiV0iucgf3rNBLjjeNEwNSTTG9F0CtQ+7JLwaE
+wSEuAuRm+pRqi8BRnQ/GKUcCAwEAAQ==
+-----END PUBLIC KEY-----
+PKDEV;
+    }
+
+    public static function getPKTags()
+    {
+        return <<<PKTAGS
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Vi/2K6apCVj76nCnCl2
+MQUPdK+A9eqkYBacXo2wQBYmyVlXm2/n/ZsX6pCLYPQTHyr5jXbkQzBw8SKqPdlh
+vA7NpbMeNCz7wP/AobvUXM8xQuXKbMDTY2uZ4O7sM+PfGbptKPBGLe8Z8d2sUnTO
+bXtX6Lrj13wkRto7st/w/Yp33RHe9SlqkiiS4MsH1jBkcIkEHsRaveZzedUaxY0M
+mba0uPhGUInpPzEHwrYqBBEtWvP97t2vtfx8I5qv28kh0Y6t+jnjL1Urid2iuQZf
+noCMFIOu4vksK5HxJxxrN0GOmGmwVQjOOtxkwikNiotZGPR4KsVj8NnBrLX7oGuM
+nQvGciiu+KoC2r3HDBrpDeBVdOWxDzT5R4iI0KoLzFh2pKqwbY+obNPS2bj+2dgJ
+rV3V5Jjry42QOCBN3c88wU1PKftOLj2ECpewY6vnE478IipiEu7EAdK8Zwj2LmTr
+RKQUSa9k7ggBkYZWAeO/2Ag0ey3g2bg7eqk+sHEq5ynIXd5lhv6tC5PBdHlWipDK
+tl2IxiEnejnOmAzGVivE1YGduYBjN+mjxDVy8KGBrjnz1JPgAvgdwJ2dYw4Rsc/e
+TzCFWGk/HM6a4f0IzBWbJ5ot0PIi4amk07IotBXDWwqDiQTwyuGCym5EqWQ2BD95
+RGv89BPD+2DLnJysngsvVaUCAwEAAQ==
+-----END PUBLIC KEY-----
+PKTAGS;
+    }
+}
+
+class ErrorHandler
+{
+    public $message;
+    protected $active;
+
+    /**
+     * Handle php errors
+     *
+     * @param mixed $code The error code
+     * @param mixed $msg The error message
+     */
+    public function handleError($code, $msg)
+    {
+        if ($this->message) {
+            $this->message .= PHP_EOL;
+        }
+        $this->message .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg);
+    }
+
+    /**
+     * Starts error-handling if not already active
+     *
+     * Any message is cleared
+     */
+    public function start()
+    {
+        if (!$this->active) {
+            set_error_handler(array($this, 'handleError'));
+            $this->active = true;
+        }
+        $this->message = '';
+    }
+
+    /**
+     * Stops error-handling if active
+     *
+     * Any message is preserved until the next call to start()
+     */
+    public function stop()
+    {
+        if ($this->active) {
+            restore_error_handler();
+            $this->active = false;
+        }
+    }
+}
+
+class NoProxyPattern
+{
+    private $composerInNoProxy = false;
+    private $rulePorts = array();
+
+    public function __construct($pattern)
+    {
+        $rules = preg_split('{[\s,]+}', $pattern, null, PREG_SPLIT_NO_EMPTY);
+
+        if ($matches = preg_grep('{getcomposer\.org(?::\d+)?}i', $rules)) {
+            $this->composerInNoProxy = true;
+
+            foreach ($matches as $match) {
+                if (strpos($match, ':') !== false) {
+                    list(, $port) = explode(':', $match);
+                    $this->rulePorts[] = (int) $port;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns true if NO_PROXY contains getcomposer.org
+     *
+     * @param string $url http(s)://getcomposer.org
+     *
+     * @return bool
+     */
+    public function test($url)
+    {
+        if (!$this->composerInNoProxy) {
+            return false;
+        }
+
+        if (empty($this->rulePorts)) {
+            return true;
+        }
+
+        if (strpos($url, 'http://') === 0) {
+            $port = 80;
+        } else {
+            $port = 443;
+        }
+
+        return in_array($port, $this->rulePorts);
+    }
+}
+
+class HttpClient {
+
+    private $options = array('http' => array());
+    private $disableTls = false;
+
+    public function __construct($disableTls = false, $cafile = false)
+    {
+        $this->disableTls = $disableTls;
+        if ($this->disableTls === false) {
+            if (!empty($cafile) && !is_dir($cafile)) {
+                if (!is_readable($cafile) || !validateCaFile(file_get_contents($cafile))) {
+                    throw new RuntimeException('The configured cafile (' .$cafile. ') was not valid or could not be read.');
+                }
+            }
+            $options = $this->getTlsStreamContextDefaults($cafile);
+            $this->options = array_replace_recursive($this->options, $options);
+        }
+    }
+
+    public function get($url)
+    {
+        $context = $this->getStreamContext($url);
+        $result = file_get_contents($url, false, $context);
+
+        if ($result && extension_loaded('zlib')) {
+            $decode = false;
+            foreach ($http_response_header as $header) {
+                if (preg_match('{^content-encoding: *gzip *$}i', $header)) {
+                    $decode = true;
+                    continue;
+                } elseif (preg_match('{^HTTP/}i', $header)) {
+                    $decode = false;
+                }
+            }
+
+            if ($decode) {
+                if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
+                    $result = zlib_decode($result);
+                } else {
+                    // work around issue with gzuncompress & co that do not work with all gzip checksums
+                    $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result));
+                }
+
+                if (!$result) {
+                    throw new RuntimeException('Failed to decode zlib stream');
+                }
+            }
+        }
+
+        return $result;
+    }
+
+    protected function getStreamContext($url)
+    {
+        if ($this->disableTls === false) {
+            if (PHP_VERSION_ID < 50600) {
+                $this->options['ssl']['SNI_server_name'] = parse_url($url, PHP_URL_HOST);
+            }
+        }
+        // Keeping the above mostly isolated from the code copied from Composer.
+        return $this->getMergedStreamContext($url);
+    }
+
+    protected function getTlsStreamContextDefaults($cafile)
+    {
+        $ciphers = implode(':', array(
+            'ECDHE-RSA-AES128-GCM-SHA256',
+            'ECDHE-ECDSA-AES128-GCM-SHA256',
+            'ECDHE-RSA-AES256-GCM-SHA384',
+            'ECDHE-ECDSA-AES256-GCM-SHA384',
+            'DHE-RSA-AES128-GCM-SHA256',
+            'DHE-DSS-AES128-GCM-SHA256',
+            'kEDH+AESGCM',
+            'ECDHE-RSA-AES128-SHA256',
+            'ECDHE-ECDSA-AES128-SHA256',
+            'ECDHE-RSA-AES128-SHA',
+            'ECDHE-ECDSA-AES128-SHA',
+            'ECDHE-RSA-AES256-SHA384',
+            'ECDHE-ECDSA-AES256-SHA384',
+            'ECDHE-RSA-AES256-SHA',
+            'ECDHE-ECDSA-AES256-SHA',
+            'DHE-RSA-AES128-SHA256',
+            'DHE-RSA-AES128-SHA',
+            'DHE-DSS-AES128-SHA256',
+            'DHE-RSA-AES256-SHA256',
+            'DHE-DSS-AES256-SHA',
+            'DHE-RSA-AES256-SHA',
+            'AES128-GCM-SHA256',
+            'AES256-GCM-SHA384',
+            'AES128-SHA256',
+            'AES256-SHA256',
+            'AES128-SHA',
+            'AES256-SHA',
+            'AES',
+            'CAMELLIA',
+            'DES-CBC3-SHA',
+            '!aNULL',
+            '!eNULL',
+            '!EXPORT',
+            '!DES',
+            '!RC4',
+            '!MD5',
+            '!PSK',
+            '!aECDH',
+            '!EDH-DSS-DES-CBC3-SHA',
+            '!EDH-RSA-DES-CBC3-SHA',
+            '!KRB5-DES-CBC3-SHA',
+        ));
+
+        /**
+         * CN_match and SNI_server_name are only known once a URL is passed.
+         * They will be set in the getOptionsForUrl() method which receives a URL.
+         *
+         * cafile or capath can be overridden by passing in those options to constructor.
+         */
+        $options = array(
+            'ssl' => array(
+                'ciphers' => $ciphers,
+                'verify_peer' => true,
+                'verify_depth' => 7,
+                'SNI_enabled' => true,
+            )
+        );
+
+        /**
+         * Attempt to find a local cafile or throw an exception.
+         * The user may go download one if this occurs.
+         */
+        if (!$cafile) {
+            $cafile = self::getSystemCaRootBundlePath();
+        }
+        if (is_dir($cafile)) {
+            $options['ssl']['capath'] = $cafile;
+        } elseif ($cafile) {
+            $options['ssl']['cafile'] = $cafile;
+        } else {
+            throw new RuntimeException('A valid cafile could not be located automatically.');
+        }
+
+        /**
+         * Disable TLS compression to prevent CRIME attacks where supported.
+         */
+        if (version_compare(PHP_VERSION, '5.4.13') >= 0) {
+            $options['ssl']['disable_compression'] = true;
+        }
+
+        return $options;
+    }
+
+    /**
+     * function copied from Composer\Util\StreamContextFactory::initOptions
+     *
+     * Any changes should be applied there as well, or backported here.
+     *
+     * @param string $url URL the context is to be used for
+     * @return resource Default context
+     * @throws \RuntimeException if https proxy required and OpenSSL uninstalled
+     */
+    protected function getMergedStreamContext($url)
+    {
+        $options = $this->options;
+
+        // Handle HTTP_PROXY/http_proxy on CLI only for security reasons
+        if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) {
+            $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']);
+        }
+
+        // Prefer CGI_HTTP_PROXY if available
+        if (!empty($_SERVER['CGI_HTTP_PROXY'])) {
+            $proxy = parse_url($_SERVER['CGI_HTTP_PROXY']);
+        }
+
+        // Override with HTTPS proxy if present and URL is https
+        if (preg_match('{^https://}i', $url) && (!empty($_SERVER['HTTPS_PROXY']) || !empty($_SERVER['https_proxy']))) {
+            $proxy = parse_url(!empty($_SERVER['https_proxy']) ? $_SERVER['https_proxy'] : $_SERVER['HTTPS_PROXY']);
+        }
+
+        // Remove proxy if URL matches no_proxy directive
+        if (!empty($_SERVER['NO_PROXY']) || !empty($_SERVER['no_proxy']) && parse_url($url, PHP_URL_HOST)) {
+            $pattern = new NoProxyPattern(!empty($_SERVER['no_proxy']) ? $_SERVER['no_proxy'] : $_SERVER['NO_PROXY']);
+            if ($pattern->test($url)) {
+                unset($proxy);
+            }
+        }
+
+        if (!empty($proxy)) {
+            $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : '';
+            $proxyURL .= isset($proxy['host']) ? $proxy['host'] : '';
+
+            if (isset($proxy['port'])) {
+                $proxyURL .= ":" . $proxy['port'];
+            } elseif (strpos($proxyURL, 'http://') === 0) {
+                $proxyURL .= ":80";
+            } elseif (strpos($proxyURL, 'https://') === 0) {
+                $proxyURL .= ":443";
+            }
+
+            // check for a secure proxy
+            if (strpos($proxyURL, 'https://') === 0) {
+                if (!extension_loaded('openssl')) {
+                    throw new RuntimeException('You must enable the openssl extension to use a secure proxy.');
+                }
+                if (strpos($url, 'https://') === 0) {
+                    throw new RuntimeException('PHP does not support https requests through a secure proxy.');
+                }
+            }
+
+            // http(s):// is not supported in proxy
+            $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL);
+
+            $options['http'] = array(
+                'proxy' => $proxyURL,
+            );
+
+            // add request_fulluri for http requests
+            if ('http' === parse_url($url, PHP_URL_SCHEME)) {
+                $options['http']['request_fulluri'] = true;
+            }
+
+            // handle proxy auth if present
+            if (isset($proxy['user'])) {
+                $auth = rawurldecode($proxy['user']);
+                if (isset($proxy['pass'])) {
+                    $auth .= ':' . rawurldecode($proxy['pass']);
+                }
+                $auth = base64_encode($auth);
+
+                $options['http']['header'] = "Proxy-Authorization: Basic {$auth}\r\n";
+            }
+        }
+
+        if (isset($options['http']['header'])) {
+            $options['http']['header'] .= "Connection: close\r\n";
+        } else {
+            $options['http']['header'] = "Connection: close\r\n";
+        }
+        if (extension_loaded('zlib')) {
+            $options['http']['header'] .= "Accept-Encoding: gzip\r\n";
+        }
+        $options['http']['header'] .= "User-Agent: ".COMPOSER_INSTALLER."\r\n";
+        $options['http']['protocol_version'] = 1.1;
+        $options['http']['timeout'] = 600;
+
+        return stream_context_create($options);
+    }
+
+    /**
+    * This method was adapted from Sslurp.
+    * https://github.com/EvanDotPro/Sslurp
+    *
+    * (c) Evan Coury <me@evancoury.com>
+    *
+    * For the full copyright and license information, please see below:
+    *
+    * Copyright (c) 2013, Evan Coury
+    * All rights reserved.
+    *
+    * Redistribution and use in source and binary forms, with or without modification,
+    * are permitted provided that the following conditions are met:
+    *
+    *     * Redistributions of source code must retain the above copyright notice,
+    *       this list of conditions and the following disclaimer.
+    *
+    *     * Redistributions in binary form must reproduce the above copyright notice,
+    *       this list of conditions and the following disclaimer in the documentation
+    *       and/or other materials provided with the distribution.
+    *
+    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+    * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+    * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+    * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+    * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+    * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+    * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+    * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+    */
+    public static function getSystemCaRootBundlePath()
+    {
+        static $caPath = null;
+
+        if ($caPath !== null) {
+            return $caPath;
+        }
+
+        // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
+        // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
+        $envCertFile = getenv('SSL_CERT_FILE');
+        if ($envCertFile && is_readable($envCertFile) && validateCaFile(file_get_contents($envCertFile))) {
+            return $caPath = $envCertFile;
+        }
+
+        // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that.
+        // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
+        $envCertDir = getenv('SSL_CERT_DIR');
+        if ($envCertDir && is_dir($envCertDir) && is_readable($envCertDir)) {
+            return $caPath = $envCertDir;
+        }
+
+        $configured = ini_get('openssl.cafile');
+        if ($configured && strlen($configured) > 0 && is_readable($configured) && validateCaFile(file_get_contents($configured))) {
+            return $caPath = $configured;
+        }
+
+        $configured = ini_get('openssl.capath');
+        if ($configured && is_dir($configured) && is_readable($configured)) {
+            return $caPath = $configured;
+        }
+
+        $caBundlePaths = array(
+            '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package)
+            '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
+            '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package)
+            '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package)
+            '/usr/ssl/certs/ca-bundle.crt', // Cygwin
+            '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
+            '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option)
+            '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
+            '/etc/ssl/cert.pem', // OpenBSD
+            '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x
+            '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package
+            '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package
+        );
+
+        foreach ($caBundlePaths as $caBundle) {
+            if (@is_readable($caBundle) && validateCaFile(file_get_contents($caBundle))) {
+                return $caPath = $caBundle;
+            }
+        }
+
+        foreach ($caBundlePaths as $caBundle) {
+            $caBundle = dirname($caBundle);
+            if (is_dir($caBundle) && glob($caBundle.'/*')) {
+                return $caPath = $caBundle;
+            }
+        }
+
+        return $caPath = false;
+    }
+
+    public static function getPackagedCaFile()
+    {
+        return <<<CACERT
+##
+## Bundle of CA Root Certificates for Let's Encrypt
+##
+## See https://letsencrypt.org/certificates/#root-certificates
+##
+## ISRG Root X1 (RSA 4096) expires Jun 04 11:04:38 2035 GMT
+## ISRG Root X2 (ECDSA P-384) expires Sep 17 16:00:00 2040 GMT
+##
+## Both these are self-signed CA root certificates
+##
+
+ISRG Root X1
+============
+-----BEGIN CERTIFICATE-----
+MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
+WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
+ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
+MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
+h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
+0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
+A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
+T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
+B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
+B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
+KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
+OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
+jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
+qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
+rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
+hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
+ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
+3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
+NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
+ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
+TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
+jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
+oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
+4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
+mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
+emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
+-----END CERTIFICATE-----
+
+ISRG Root X2
+============
+-----BEGIN CERTIFICATE-----
+MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
+CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
+R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
+MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
+ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
+EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
+ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
+zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
+tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
+/q4AaOeMSQ+2b1tbFfLn
+-----END CERTIFICATE-----
+CACERT;
+    }
+}

+ 12 - 12
src/composer.json

@@ -1,12 +1,12 @@
-{
-    "require": {
-        "vlucas/phpdotenv": "^5.6",
-        "components/jquery": "^3.7"
-    },
-    "scripts": {
-        "post-update-cmd": [
-            "rm -rf jquery",
-            "cp -R vendor/components/jquery/ jquery"
-        ]
-    }
-}
+{
+    "require": {
+        "vlucas/phpdotenv": "^5.6",
+        "components/jquery": "^3.7"
+    },
+    "scripts": {
+        "post-update-cmd": [
+            "rm -rf jquery",
+            "cp -R vendor/components/jquery/ jquery"
+        ]
+    }
+}

+ 537 - 537
src/composer.lock

@@ -1,537 +1,537 @@
-{
-    "_readme": [
-        "This file locks the dependencies of your project to a known state",
-        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
-        "This file is @generated automatically"
-    ],
-    "content-hash": "09a1c10dbb684a8a74191f6de83945f6",
-    "packages": [
-        {
-            "name": "components/jquery",
-            "version": "v3.7.1",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/components/jquery.git",
-                "reference": "8edc7785239bb8c2ad2b83302b856a1d61de60e7"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/components/jquery/zipball/8edc7785239bb8c2ad2b83302b856a1d61de60e7",
-                "reference": "8edc7785239bb8c2ad2b83302b856a1d61de60e7",
-                "shasum": ""
-            },
-            "type": "component",
-            "extra": {
-                "component": {
-                    "scripts": [
-                        "jquery.js"
-                    ],
-                    "files": [
-                        "jquery.min.js",
-                        "jquery.min.map",
-                        "jquery.slim.js",
-                        "jquery.slim.min.js",
-                        "jquery.slim.min.map"
-                    ]
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "JS Foundation and other contributors"
-                }
-            ],
-            "description": "jQuery JavaScript Library",
-            "homepage": "http://jquery.com",
-            "support": {
-                "forum": "http://forum.jquery.com",
-                "irc": "irc://irc.freenode.org/jquery",
-                "issues": "https://github.com/jquery/jquery/issues",
-                "source": "https://github.com/jquery/jquery",
-                "wiki": "http://docs.jquery.com/"
-            },
-            "time": "2023-09-22T01:43:46+00:00"
-        },
-        {
-            "name": "graham-campbell/result-type",
-            "version": "v1.1.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/GrahamCampbell/Result-Type.git",
-                "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862",
-                "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2.5 || ^8.0",
-                "phpoption/phpoption": "^1.9.2"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
-            },
-            "type": "library",
-            "autoload": {
-                "psr-4": {
-                    "GrahamCampbell\\ResultType\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Graham Campbell",
-                    "email": "hello@gjcampbell.co.uk",
-                    "homepage": "https://github.com/GrahamCampbell"
-                }
-            ],
-            "description": "An Implementation Of The Result Type",
-            "keywords": [
-                "Graham Campbell",
-                "GrahamCampbell",
-                "Result Type",
-                "Result-Type",
-                "result"
-            ],
-            "support": {
-                "issues": "https://github.com/GrahamCampbell/Result-Type/issues",
-                "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/GrahamCampbell",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-11-12T22:16:48+00:00"
-        },
-        {
-            "name": "phpoption/phpoption",
-            "version": "1.9.2",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/schmittjoh/php-option.git",
-                "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820",
-                "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820",
-                "shasum": ""
-            },
-            "require": {
-                "php": "^7.2.5 || ^8.0"
-            },
-            "require-dev": {
-                "bamarni/composer-bin-plugin": "^1.8.2",
-                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
-            },
-            "type": "library",
-            "extra": {
-                "bamarni-bin": {
-                    "bin-links": true,
-                    "forward-command": true
-                },
-                "branch-alias": {
-                    "dev-master": "1.9-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "PhpOption\\": "src/PhpOption/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "Apache-2.0"
-            ],
-            "authors": [
-                {
-                    "name": "Johannes M. Schmitt",
-                    "email": "schmittjoh@gmail.com",
-                    "homepage": "https://github.com/schmittjoh"
-                },
-                {
-                    "name": "Graham Campbell",
-                    "email": "hello@gjcampbell.co.uk",
-                    "homepage": "https://github.com/GrahamCampbell"
-                }
-            ],
-            "description": "Option Type for PHP",
-            "keywords": [
-                "language",
-                "option",
-                "php",
-                "type"
-            ],
-            "support": {
-                "issues": "https://github.com/schmittjoh/php-option/issues",
-                "source": "https://github.com/schmittjoh/php-option/tree/1.9.2"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/GrahamCampbell",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-11-12T21:59:55+00:00"
-        },
-        {
-            "name": "symfony/polyfill-ctype",
-            "version": "v1.28.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
-                "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "provide": {
-                "ext-ctype": "*"
-            },
-            "suggest": {
-                "ext-ctype": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.28-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Ctype\\": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Gert de Pagter",
-                    "email": "BackEndTea@gmail.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for ctype functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "ctype",
-                "polyfill",
-                "portable"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-01-26T09:26:14+00:00"
-        },
-        {
-            "name": "symfony/polyfill-mbstring",
-            "version": "v1.28.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "42292d99c55abe617799667f454222c54c60e229"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
-                "reference": "42292d99c55abe617799667f454222c54c60e229",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "provide": {
-                "ext-mbstring": "*"
-            },
-            "suggest": {
-                "ext-mbstring": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.28-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Mbstring\\": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for the Mbstring extension",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "mbstring",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-07-28T09:04:16+00:00"
-        },
-        {
-            "name": "symfony/polyfill-php80",
-            "version": "v1.28.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-php80.git",
-                "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
-                "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.28-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Php80\\": ""
-                },
-                "classmap": [
-                    "Resources/stubs"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Ion Bazan",
-                    "email": "ion.bazan@gmail.com"
-                },
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-01-26T09:26:14+00:00"
-        },
-        {
-            "name": "vlucas/phpdotenv",
-            "version": "v5.6.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/vlucas/phpdotenv.git",
-                "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
-                "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
-                "shasum": ""
-            },
-            "require": {
-                "ext-pcre": "*",
-                "graham-campbell/result-type": "^1.1.2",
-                "php": "^7.2.5 || ^8.0",
-                "phpoption/phpoption": "^1.9.2",
-                "symfony/polyfill-ctype": "^1.24",
-                "symfony/polyfill-mbstring": "^1.24",
-                "symfony/polyfill-php80": "^1.24"
-            },
-            "require-dev": {
-                "bamarni/composer-bin-plugin": "^1.8.2",
-                "ext-filter": "*",
-                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
-            },
-            "suggest": {
-                "ext-filter": "Required to use the boolean validator."
-            },
-            "type": "library",
-            "extra": {
-                "bamarni-bin": {
-                    "bin-links": true,
-                    "forward-command": true
-                },
-                "branch-alias": {
-                    "dev-master": "5.6-dev"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Dotenv\\": "src/"
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "BSD-3-Clause"
-            ],
-            "authors": [
-                {
-                    "name": "Graham Campbell",
-                    "email": "hello@gjcampbell.co.uk",
-                    "homepage": "https://github.com/GrahamCampbell"
-                },
-                {
-                    "name": "Vance Lucas",
-                    "email": "vance@vancelucas.com",
-                    "homepage": "https://github.com/vlucas"
-                }
-            ],
-            "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
-            "keywords": [
-                "dotenv",
-                "env",
-                "environment"
-            ],
-            "support": {
-                "issues": "https://github.com/vlucas/phpdotenv/issues",
-                "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0"
-            },
-            "funding": [
-                {
-                    "url": "https://github.com/GrahamCampbell",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-11-12T22:43:29+00:00"
-        }
-    ],
-    "packages-dev": [],
-    "aliases": [],
-    "minimum-stability": "stable",
-    "stability-flags": [],
-    "prefer-stable": false,
-    "prefer-lowest": false,
-    "platform": [],
-    "platform-dev": [],
-    "plugin-api-version": "2.0.0"
-}
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "09a1c10dbb684a8a74191f6de83945f6",
+    "packages": [
+        {
+            "name": "components/jquery",
+            "version": "v3.7.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/components/jquery.git",
+                "reference": "8edc7785239bb8c2ad2b83302b856a1d61de60e7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/components/jquery/zipball/8edc7785239bb8c2ad2b83302b856a1d61de60e7",
+                "reference": "8edc7785239bb8c2ad2b83302b856a1d61de60e7",
+                "shasum": ""
+            },
+            "type": "component",
+            "extra": {
+                "component": {
+                    "scripts": [
+                        "jquery.js"
+                    ],
+                    "files": [
+                        "jquery.min.js",
+                        "jquery.min.map",
+                        "jquery.slim.js",
+                        "jquery.slim.min.js",
+                        "jquery.slim.min.map"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "JS Foundation and other contributors"
+                }
+            ],
+            "description": "jQuery JavaScript Library",
+            "homepage": "http://jquery.com",
+            "support": {
+                "forum": "http://forum.jquery.com",
+                "irc": "irc://irc.freenode.org/jquery",
+                "issues": "https://github.com/jquery/jquery/issues",
+                "source": "https://github.com/jquery/jquery",
+                "wiki": "http://docs.jquery.com/"
+            },
+            "time": "2023-09-22T01:43:46+00:00"
+        },
+        {
+            "name": "graham-campbell/result-type",
+            "version": "v1.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/GrahamCampbell/Result-Type.git",
+                "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862",
+                "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0",
+                "phpoption/phpoption": "^1.9.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "GrahamCampbell\\ResultType\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                }
+            ],
+            "description": "An Implementation Of The Result Type",
+            "keywords": [
+                "Graham Campbell",
+                "GrahamCampbell",
+                "Result Type",
+                "Result-Type",
+                "result"
+            ],
+            "support": {
+                "issues": "https://github.com/GrahamCampbell/Result-Type/issues",
+                "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-11-12T22:16:48+00:00"
+        },
+        {
+            "name": "phpoption/phpoption",
+            "version": "1.9.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/schmittjoh/php-option.git",
+                "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820",
+                "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2.5 || ^8.0"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": true
+                },
+                "branch-alias": {
+                    "dev-master": "1.9-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpOption\\": "src/PhpOption/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Johannes M. Schmitt",
+                    "email": "schmittjoh@gmail.com",
+                    "homepage": "https://github.com/schmittjoh"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                }
+            ],
+            "description": "Option Type for PHP",
+            "keywords": [
+                "language",
+                "option",
+                "php",
+                "type"
+            ],
+            "support": {
+                "issues": "https://github.com/schmittjoh/php-option/issues",
+                "source": "https://github.com/schmittjoh/php-option/tree/1.9.2"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-11-12T21:59:55+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.28.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
+                "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "provide": {
+                "ext-ctype": "*"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.28-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-01-26T09:26:14+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.28.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "42292d99c55abe617799667f454222c54c60e229"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
+                "reference": "42292d99c55abe617799667f454222c54c60e229",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "provide": {
+                "ext-mbstring": "*"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.28-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-07-28T09:04:16+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.28.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
+                "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.28-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-01-26T09:26:14+00:00"
+        },
+        {
+            "name": "vlucas/phpdotenv",
+            "version": "v5.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/vlucas/phpdotenv.git",
+                "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
+                "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4",
+                "shasum": ""
+            },
+            "require": {
+                "ext-pcre": "*",
+                "graham-campbell/result-type": "^1.1.2",
+                "php": "^7.2.5 || ^8.0",
+                "phpoption/phpoption": "^1.9.2",
+                "symfony/polyfill-ctype": "^1.24",
+                "symfony/polyfill-mbstring": "^1.24",
+                "symfony/polyfill-php80": "^1.24"
+            },
+            "require-dev": {
+                "bamarni/composer-bin-plugin": "^1.8.2",
+                "ext-filter": "*",
+                "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+            },
+            "suggest": {
+                "ext-filter": "Required to use the boolean validator."
+            },
+            "type": "library",
+            "extra": {
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": true
+                },
+                "branch-alias": {
+                    "dev-master": "5.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Dotenv\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Graham Campbell",
+                    "email": "hello@gjcampbell.co.uk",
+                    "homepage": "https://github.com/GrahamCampbell"
+                },
+                {
+                    "name": "Vance Lucas",
+                    "email": "vance@vancelucas.com",
+                    "homepage": "https://github.com/vlucas"
+                }
+            ],
+            "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
+            "keywords": [
+                "dotenv",
+                "env",
+                "environment"
+            ],
+            "support": {
+                "issues": "https://github.com/vlucas/phpdotenv/issues",
+                "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/GrahamCampbell",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-11-12T22:43:29+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": [],
+    "plugin-api-version": "2.0.0"
+}

BIN
src/composer.phar


+ 23 - 23
src/config.php

@@ -1,24 +1,24 @@
-<?php
-// Файл настроек
-
-// Установка временного пояса по умолчанию (GMT+3)
-date_default_timezone_set("Europe/Kirov");
-
-define('rootdir', __DIR__);
-
-require_once rootdir."/vendor/autoload.php";
-
-if (strtoupper(substr(php_uname("s"), 0, 3)) === 'WIN') {
-    define("server_os", "windows");
-} else {
-    define("server_os", "linux");
-}
-
-$dotenv = Dotenv\Dotenv::createImmutable(rootdir);
-$dotenv->load();
-
-// regen
-define("regen_surname", "Королёв");
-define("regen_full", "Королёв В.С.");
-define("regen_group", "3ИС");
+<?php
+// Файл настроек
+
+// Установка временного пояса по умолчанию (GMT+3)
+date_default_timezone_set("Europe/Kirov");
+
+define('rootdir', __DIR__);
+
+require_once rootdir."/vendor/autoload.php";
+
+if (strtoupper(substr(php_uname("s"), 0, 3)) === 'WIN') {
+    define("server_os", "windows");
+} else {
+    define("server_os", "linux");
+}
+
+$dotenv = Dotenv\Dotenv::createImmutable(rootdir);
+$dotenv->load();
+
+// regen
+define("regen_surname", "Королёв");
+define("regen_full", "Королёв В.С.");
+define("regen_group", "3ИС");
 define("regen_code", ".012.09.02.07.000");

+ 1 - 1
src/controllers/RegenController.php

@@ -57,7 +57,7 @@ class RegenController extends Controller {
 	}
 
 	// Редактирование отчёта
-	public function edit($report_id) {
+	public static function edit($report_id) {
 		$report = ReportModel::getById($report_id);
 		$subject = SubjectModel::getById($report['subject_id']);
 

+ 36 - 36
src/css/grades.css

@@ -1,37 +1,37 @@
-table {
-	border: 1px solid #404040;
-	box-shadow: 0px 0px 5px 5px #404040;
-	border-radius: 10px;
-	margin: 2em auto;
-	border-collapse: collapse;
-	font-size: 16pt;
-}
-
-td, th {
-	padding: 0.5em;
-}
-
-tr {
-	color: #ccc;
-}
-
-tr:nth-child(even) {
-  background: #2B2B2B;
-}
-tr:nth-child(odd) {
-  background: #303030;
-}
-
-.problem:nth-child(even) {
-  background: linear-gradient(to right, #2B2B2B, #DC1E1E);
-}
-.problem:nth-child(odd) {
-  background: linear-gradient(to right, #303030, #D22828);
-}
-
-.perfect:nth-child(even) {
-  background: linear-gradient(to right, #2B2B2B, #A028B4);
-}
-.perfect:nth-child(odd) {
-  background: linear-gradient(to right, #303030, #9628C8);
+table {
+	border: 1px solid #404040;
+	box-shadow: 0px 0px 5px 5px #404040;
+	border-radius: 10px;
+	margin: 2em auto;
+	border-collapse: collapse;
+	font-size: 16pt;
+}
+
+td, th {
+	padding: 0.5em;
+}
+
+tr {
+	color: #ccc;
+}
+
+tr:nth-child(even) {
+  background: #2B2B2B;
+}
+tr:nth-child(odd) {
+  background: #303030;
+}
+
+.problem:nth-child(even) {
+  background: linear-gradient(to right, #2B2B2B, #DC1E1E);
+}
+.problem:nth-child(odd) {
+  background: linear-gradient(to right, #303030, #D22828);
+}
+
+.perfect:nth-child(even) {
+  background: linear-gradient(to right, #2B2B2B, #A028B4);
+}
+.perfect:nth-child(odd) {
+  background: linear-gradient(to right, #303030, #9628C8);
 }

+ 8 - 8
src/css/home.css

@@ -1,9 +1,9 @@
-body {
-  background-image: url('/img/wallpapers/train.jpg');
-  background-color: #396690;
-  background-size: cover;
-}
-
-html {
-    height: 100%;
+body {
+  background-image: url('/img/wallpapers/train.jpg');
+  background-color: #396690;
+  background-size: cover;
+}
+
+html {
+    height: 100%;
 }

+ 272 - 272
src/css/pockit.css

@@ -1,273 +1,273 @@
-:root {
-	--gray-9: #E6E6E6;
-	--gray-8: #CCCCCC;
-	--gray-7: #B3B3B3;
-	--gray-6: #4D4D4D;
-	--gray-5: #404040;
-	--gray-4: #333333;
-	--gray-3: #252525;
-	--gray-2: #1f1f1f;
-	--gray-1: #1c1c1c;
-	--gray-0: #161616;
-
-	--error: #4B2224;
-
-	--success: #2B4B22;
-	--successhover: #3A662E;
-	--successfocus: #25401D;
-	--successborder: green;
-
-	--blue: #006DFF;
-	--accent: #396690;
-}
-
-@font-face{font-family:'lato'; src: url('/fonts/Lato-Bold.ttf') format('truetype'); font-weight: bold; font-style: normal;}
-@font-face{font-family:'lato'; src: url('/fonts/Lato-BoldItalic.ttf') format('truetype'); font-weight:bold; font-style: italic;}
-@font-face{font-family:'lato'; src: url('/fonts/Lato-Regular.ttf') format('truetype'); font-weight:normal; font-style: normal;}
-
-body {
-	background-color: var(--gray-1);
-	color: var(--gray-8);
-	font-family: 'lato';
-	font-size: 18px;
-	margin: 0;
-}
-
-h1 {
-	margin: 0;
-}
-
-nav {
-	background-color: var(--gray-0);
-	border-bottom: 1px solid var(--gray-2);
-}
-
-/* Colors */
-.danger {
-	background-color: var(--error);
-}
-/* End Colors */
-
-/* Forms */
-.form-control-container {
-	margin: 1em auto;
-	position: relative;
-	display: flex;
-	flex-flow: column;
-}
-
-.form-control-container label {
-	color: var(--gray-9)
-}
-
-.form-control {
-	padding: 0.5em;
-	margin: 0.1em 0;
-	width: 1fr;
-	background-color: var(--gray-4);
-	color: var(--gray-7);
-	outline: none;
-	border-radius: 5px;
-	font-size: 16px;
-	border: 1px solid transparent;
-}
-
-.form-control:hover,
-.form-control:focus {
-	border-color: var(--gray-6);
-}
-
-.form-control:focus {
-	color: var(--gray-9);
-}
-
-/* End Forms */
-
-/* Buttons */
-button,
-input {
-	background-color: var(--gray-4);
-	border: 1px solid var(--gray-5);
-	padding: 0.5em;
-	color: var(--gray-9);
-	font-size: 1rem;
-	border-radius: 5px;
-}
-
-button:hover {
-	background-color: var(--gray-6);
-}
-
-button:active,
-button:focus {
-	background-color: var(--gray-3);
-}
-
-.createbutton {
-	background-color: var(--success);
-	border-color: var(--successborder);
-	color: var(--gray-9);
-	width: 100%;
-}
-.createbutton:hover {
-	background-color: var(--successhover);
-}
-.createbutton:active,
-.createbutton:focus {
-	background-color: var(--successfocus);
-}
-
-a {
-	text-decoration: underline;
-}
-
-a:hover {
-	text-decoration: none;
-}
-
-.text-center {
-	text-align: center;
-}
-
-.actions {
-	display: flex;
-}
-
-.actions-row {
-	display: flex;
-	flex-flow: row;
-	width: 100%;
-	justify-content: center;
-}
-
-.action {
-	display: flex;
-	flex-flow: column;
-	text-align: center;
-	position: relative;
-	background-color: transparent;
-	border-radius: 10px;
-	padding: 1em;
-	transition: 100ms;
-}
-
-.action:hover {
-	background-color: rgba(255, 255, 255, 0.3);
-}
-
-.action > img {
-	max-width: 128px;
-	margin-top: 1em;
-}
-
-.action > a {
-	display: block;
-	width: 100%;
-	height: 100%;
-	color: var(--gray-8);
-}
-
-.stretched-link {
-	position: absolute;
-	width: 100%;
-	height: 100%;
-	top: 0;
-	left: 0;
-	z-index: 1;
-}
-
-/* Breadcrumbs */
-.breadcrumb {
-	padding: 0 0.5em;
-}
-
-.breadcrumb-item {
-	padding: 0.5em 0;
-}
-
-.breadcrumb ul {
-  display: flex;
-  flex-wrap: wrap;
-  list-style: none;
-  margin: 0;
-  padding: 0;
-}
-
-.breadcrumb li:not(:last-child)::after {
-  display: inline-block;
-  margin: 0 .25rem;
-  content: "→";
-  color: var(--gray-7);
-}
-
-.breadcrumb-item a {
-	color: var(--accent);
-}
-
-.breadcrumb-item.active {
-	color: var(--gray-7);
-}
-
-/* End Breadcrumbs */
-
-/* Cards */
-.card {
-	background-color: var(--gray-3);
-	border: 1px solid var(--gray-4);
-	border-radius: 15px;
-	padding: 1em;
-	margin: 2em auto;
-	width: 80%;
-	max-width: 866px;
-}
-
-.card.error {
-	background-color: #4B2224;
-	border-color: red;
-	color: #f9fbf8;
-}
-
-.card.modal {
-	position: fixed;
-	top: 10%;
-	left: 0;
-	right: 0;
-	z-index: 2;
-}
-/* End Cards */
-
-.filename {
-	user-select: all;
-	font-weight: bold;
-	text-decoration: underline;
-}
-
-/* CRUD */
-.crud-item {
-	margin: 1rem auto;
-	padding: 1em;
-	background-color: var(--gray-2);
-	border-radius: 5px;
-	display: grid;
-	grid-template-columns: 75% 25%;
-}
-
-.crud-buttons {
-	display: flex;
-	flex-direction: column;
-}
-
-.crud-buttons button {
-	margin: 0.1em;
-}
-/* End CRUD */
-
-.dark-overlay {
-	background-color: rgba(0,0,0,0.5);
-	position: fixed;
-	top: 0;
-	bottom: 0;
-	left: 0;
-	right: 0;
-	z-index: 1;
+:root {
+	--gray-9: #E6E6E6;
+	--gray-8: #CCCCCC;
+	--gray-7: #B3B3B3;
+	--gray-6: #4D4D4D;
+	--gray-5: #404040;
+	--gray-4: #333333;
+	--gray-3: #252525;
+	--gray-2: #1f1f1f;
+	--gray-1: #1c1c1c;
+	--gray-0: #161616;
+
+	--error: #4B2224;
+
+	--success: #2B4B22;
+	--successhover: #3A662E;
+	--successfocus: #25401D;
+	--successborder: green;
+
+	--blue: #006DFF;
+	--accent: #396690;
+}
+
+@font-face{font-family:'lato'; src: url('/fonts/Lato-Bold.ttf') format('truetype'); font-weight: bold; font-style: normal;}
+@font-face{font-family:'lato'; src: url('/fonts/Lato-BoldItalic.ttf') format('truetype'); font-weight:bold; font-style: italic;}
+@font-face{font-family:'lato'; src: url('/fonts/Lato-Regular.ttf') format('truetype'); font-weight:normal; font-style: normal;}
+
+body {
+	background-color: var(--gray-1);
+	color: var(--gray-8);
+	font-family: 'lato';
+	font-size: 18px;
+	margin: 0;
+}
+
+h1 {
+	margin: 0;
+}
+
+nav {
+	background-color: var(--gray-0);
+	border-bottom: 1px solid var(--gray-2);
+}
+
+/* Colors */
+.danger {
+	background-color: var(--error);
+}
+/* End Colors */
+
+/* Forms */
+.form-control-container {
+	margin: 1em auto;
+	position: relative;
+	display: flex;
+	flex-flow: column;
+}
+
+.form-control-container label {
+	color: var(--gray-9)
+}
+
+.form-control {
+	padding: 0.5em;
+	margin: 0.1em 0;
+	width: 1fr;
+	background-color: var(--gray-4);
+	color: var(--gray-7);
+	outline: none;
+	border-radius: 5px;
+	font-size: 16px;
+	border: 1px solid transparent;
+}
+
+.form-control:hover,
+.form-control:focus {
+	border-color: var(--gray-6);
+}
+
+.form-control:focus {
+	color: var(--gray-9);
+}
+
+/* End Forms */
+
+/* Buttons */
+button,
+input {
+	background-color: var(--gray-4);
+	border: 1px solid var(--gray-5);
+	padding: 0.5em;
+	color: var(--gray-9);
+	font-size: 1rem;
+	border-radius: 5px;
+}
+
+button:hover {
+	background-color: var(--gray-6);
+}
+
+button:active,
+button:focus {
+	background-color: var(--gray-3);
+}
+
+.createbutton {
+	background-color: var(--success);
+	border-color: var(--successborder);
+	color: var(--gray-9);
+	width: 100%;
+}
+.createbutton:hover {
+	background-color: var(--successhover);
+}
+.createbutton:active,
+.createbutton:focus {
+	background-color: var(--successfocus);
+}
+
+a {
+	text-decoration: underline;
+}
+
+a:hover {
+	text-decoration: none;
+}
+
+.text-center {
+	text-align: center;
+}
+
+.actions {
+	display: flex;
+}
+
+.actions-row {
+	display: flex;
+	flex-flow: row;
+	width: 100%;
+	justify-content: center;
+}
+
+.action {
+	display: flex;
+	flex-flow: column;
+	text-align: center;
+	position: relative;
+	background-color: transparent;
+	border-radius: 10px;
+	padding: 1em;
+	transition: 100ms;
+}
+
+.action:hover {
+	background-color: rgba(255, 255, 255, 0.3);
+}
+
+.action > img {
+	max-width: 128px;
+	margin-top: 1em;
+}
+
+.action > a {
+	display: block;
+	width: 100%;
+	height: 100%;
+	color: var(--gray-8);
+}
+
+.stretched-link {
+	position: absolute;
+	width: 100%;
+	height: 100%;
+	top: 0;
+	left: 0;
+	z-index: 1;
+}
+
+/* Breadcrumbs */
+.breadcrumb {
+	padding: 0 0.5em;
+}
+
+.breadcrumb-item {
+	padding: 0.5em 0;
+}
+
+.breadcrumb ul {
+  display: flex;
+  flex-wrap: wrap;
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+
+.breadcrumb li:not(:last-child)::after {
+  display: inline-block;
+  margin: 0 .25rem;
+  content: "→";
+  color: var(--gray-7);
+}
+
+.breadcrumb-item a {
+	color: var(--accent);
+}
+
+.breadcrumb-item.active {
+	color: var(--gray-7);
+}
+
+/* End Breadcrumbs */
+
+/* Cards */
+.card {
+	background-color: var(--gray-3);
+	border: 1px solid var(--gray-4);
+	border-radius: 15px;
+	padding: 1em;
+	margin: 2em auto;
+	width: 80%;
+	max-width: 866px;
+}
+
+.card.error {
+	background-color: #4B2224;
+	border-color: red;
+	color: #f9fbf8;
+}
+
+.card.modal {
+	position: fixed;
+	top: 10%;
+	left: 0;
+	right: 0;
+	z-index: 2;
+}
+/* End Cards */
+
+.filename {
+	user-select: all;
+	font-weight: bold;
+	text-decoration: underline;
+}
+
+/* CRUD */
+.crud-item {
+	margin: 1rem auto;
+	padding: 1em;
+	background-color: var(--gray-2);
+	border-radius: 5px;
+	display: grid;
+	grid-template-columns: 75% 25%;
+}
+
+.crud-buttons {
+	display: flex;
+	flex-direction: column;
+}
+
+.crud-buttons button {
+	margin: 0.1em;
+}
+/* End CRUD */
+
+.dark-overlay {
+	background-color: rgba(0,0,0,0.5);
+	position: fixed;
+	top: 0;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	z-index: 1;
 }

+ 194 - 194
src/css/regen-report.css

@@ -1,195 +1,195 @@
-#controls > textarea {
-	width: -moz-available;
-	resize: vertical;
-	background-color: var(--gray-4);
-	outline: none;
-	border: 1px solid var(--card-border);
-	color: var(--gray-8);
-	border-radius: 5px;
-	font-size: 1rem;
-}
-
-#control-buttons {
-	margin-bottom: 1em;
-}
-
-#preview {
-	font-family: timesnewroman;
-	background-color: #383838;
-	color: #aaa;
-	font-size: 12pt;
-	margin: 0;
-	overflow: scroll;
-}
-
-#preview span {
-	position: absolute;
-	font-size: 12px;
-	user-select: none;
-}
-
-/* Контейнер страницы */
-#preview .page-container {
-	padding: 16px;
-}
-
-/* Разделитель */
-#preview hr {
-	width: 50%;
-}
-
-/* Все страницы */
-#preview .page {
-	background-position:2cm 1cm;
-	background-size: 18.5cm 28.2cm;
-	box-sizing: border-box;
-	width: 21cm;
-	height: 29.7cm;
-	padding: 2cm 1.5cm 0cm 3cm;
-	background-repeat:no-repeat;
-	position: relative;
-	margin: auto;
-	overflow: hidden;
-	color:#333333;
-	background-color:#ddd;
-}
-
-/* Страницы с большой рамкой */
-#preview .page.big {
-	background-image: url(/img/regen/bigframe.gif);
-}
-
-/* Страницы с маленькой рамкой */
-#preview .page.small {
-	background-image: url(/img/regen/smallframe.gif);
-}
-
-/* Страницы без рамки */
-#preview .page.tpage,
-#preview .page.apage {
-	padding: 2cm
-}
-
-/* Заголовок (по центру) */
-#preview .page .title {
-	text-align:center;
-	text-align-last:center;
-	text-indent:0cm;
-}
-
-/* Текст по правому краю */
-#preview .page .r {
-	text-align:right;
-	text-align-last:right;
-	text-indent:0cm;
-}
-
-/* Обычный текст */
-#preview .page p {
-	line-height: 1.5;
-	padding: 0;
-	margin: 0;
-	text-align:justify;
-	text-align-last:left;
-	text-indent:1.25cm;
-}
-
-/* Заголовок таблицы */
-#preview .page .tt {
-	text-indent:0;
-}
-
-/* Изображение */
-#preview img {
-	max-width: 12cm;
-	margin:0.5cm auto 0.25cm auto;
-	display:block;
-	border:1px solid #000;
-}
-
-/* Таблицы */
-table, tr, td, th {border: 1px solid; border-collapse: collapse;}
-table {width:100%;}
-td {padding: 0 0.3cm;vertical-align:top;}
-th {text-align:center !important;font-weight:normal}
-
-/* Текст в рамках */
-#preview .iz::after {content: "Изм.";}
-#preview .ls::after,
-#preview .pl::after {content: "Лист";}
-#preview .nd::after {content: "№ докум.";}
-#preview .pd::after {content: "Подпись";}
-#preview .dt::after {content: "Дата";}
-#preview .rz::after {content: "Разраб.";}
-#preview .lt::after {content: "Лит.";}
-#preview .al::after {content: "Листов";}
-#preview .pr::after {content: "Провер.";}
-#preview .nc::after {content: "Н. Контр.";}
-#preview .ut::after {content: "Утверд.";}
-
-#preview .page.big .co {left:9.2cm; bottom: 3.5cm; right:0.6cm;font-size:22px;text-align:center;}
-#preview .page.big .iz {left:2.1cm;bottom:3.1cm;}
-#preview .page.big .ls {left:3.15cm;bottom:3.1cm;}
-#preview .page.big .nd {left:4.7cm;bottom:3.1cm;}
-#preview .page.big .pd {left:6.6cm;bottom:3.1cm;}
-#preview .page.big .dt {left:8.24cm;bottom:3.1cm;}
-#preview .page.big .rz {left:2.15cm;bottom:2.6cm;}
-#preview .page.big .sr {left:4.13cm; bottom:2.6cm;}
-#preview .page.big .lt {left:15.6cm;bottom:2.6cm;}
-#preview .page.big .pl {left:17.1cm;bottom:2.6cm;}
-#preview .page.big .al {left:18.8cm;bottom:2.6cm;}
-#preview .page.big .pr {left:2.15cm;bottom:2.1cm;}
-#preview .page.big .st {left:4.13cm; bottom:2.1cm;}
-#preview .page.big .cp {left:16.71cm;right:2.9cm;bottom:2.1cm;text-align:center;}
-#preview .page.big .pc {left:18.1cm;right:0.6cm;bottom:2.1cm;text-align:center;}
-#preview .page.big .nm {left:9.1cm;bottom:1.6cm;right:5.85cm;font-size:16px;text-align:center;}
-#preview .page.big .gr {left:15.25cm;right:0.6cm;bottom:1cm;text-align:center;font-size:20px;}
-#preview .page.big .nc {left:2.15cm;bottom:1.1cm;}
-#preview .page.big .ut {left:2.15cm;bottom:0.6cm;}
-
-#preview .page.small .iz {left:2.15cm;bottom:0.6cm;}
-#preview .page.small .ls {left:3.15cm;bottom:0.6cm;}
-#preview .page.small .nd {left:4.61cm;bottom:0.6cm;}
-#preview .page.small .pd {left:6.6cm;bottom:0.6cm;}
-#preview .page.small .dt {left:8.25cm;bottom:0.6cm;}
-#preview .page.small .co {left:9.1cm;bottom:1.0cm;right:1.5cm;font-size:22px;text-align:center;}
-#preview .page.small .pl {left:19.55cm;bottom:1.55cm;}
-#preview .page.small .cp {left:19.5cm;bottom:0.90cm;right:0.5cm;text-align:center;}
-
-.opt {display:block;text-align:center !important;font-size:18px;color:white;font-family:sans;margin: 8px 0px;user-select:none;}
-.opt:hover {color:#005BFF;}
-
-@media print {
-	nav,
-	#markuparea,
-	button,
-	#preview hr,
-	#control-buttons,
-	.no-print {
-		display: none;
-	}
-	
-	#controls {
-		visibility: hidden;
-		width: auto;
-		margin: 0;
-		padding: 0;
-		max-width: unset;
-		border: none;
-		border-radius: 0px;
-	}
-
-	#preview {
-		display: block !important;
-		visibility: visible;
-	}
-
-	#preview .page-container {
-		padding: 0;
-	}
-
-	#preview .page {
-		background-color: white;
-		color: black;
-	}
+#controls > textarea {
+	width: -moz-available;
+	resize: vertical;
+	background-color: var(--gray-4);
+	outline: none;
+	border: 1px solid var(--card-border);
+	color: var(--gray-8);
+	border-radius: 5px;
+	font-size: 1rem;
+}
+
+#control-buttons {
+	margin-bottom: 1em;
+}
+
+#preview {
+	font-family: timesnewroman;
+	background-color: #383838;
+	color: #aaa;
+	font-size: 12pt;
+	margin: 0;
+	overflow: scroll;
+}
+
+#preview span {
+	position: absolute;
+	font-size: 12px;
+	user-select: none;
+}
+
+/* Контейнер страницы */
+#preview .page-container {
+	padding: 16px;
+}
+
+/* Разделитель */
+#preview hr {
+	width: 50%;
+}
+
+/* Все страницы */
+#preview .page {
+	background-position:2cm 1cm;
+	background-size: 18.5cm 28.2cm;
+	box-sizing: border-box;
+	width: 21cm;
+	height: 29.7cm;
+	padding: 2cm 1.5cm 0cm 3cm;
+	background-repeat:no-repeat;
+	position: relative;
+	margin: auto;
+	overflow: hidden;
+	color:#333333;
+	background-color:#ddd;
+}
+
+/* Страницы с большой рамкой */
+#preview .page.big {
+	background-image: url(/img/regen/bigframe.gif);
+}
+
+/* Страницы с маленькой рамкой */
+#preview .page.small {
+	background-image: url(/img/regen/smallframe.gif);
+}
+
+/* Страницы без рамки */
+#preview .page.tpage,
+#preview .page.apage {
+	padding: 2cm
+}
+
+/* Заголовок (по центру) */
+#preview .page .title {
+	text-align:center;
+	text-align-last:center;
+	text-indent:0cm;
+}
+
+/* Текст по правому краю */
+#preview .page .r {
+	text-align:right;
+	text-align-last:right;
+	text-indent:0cm;
+}
+
+/* Обычный текст */
+#preview .page p {
+	line-height: 1.5;
+	padding: 0;
+	margin: 0;
+	text-align:justify;
+	text-align-last:left;
+	text-indent:1.25cm;
+}
+
+/* Заголовок таблицы */
+#preview .page .tt {
+	text-indent:0;
+}
+
+/* Изображение */
+#preview img {
+	max-width: 12cm;
+	margin:0.5cm auto 0.25cm auto;
+	display:block;
+	border:1px solid #000;
+}
+
+/* Таблицы */
+table, tr, td, th {border: 1px solid; border-collapse: collapse;}
+table {width:100%;}
+td {padding: 0 0.3cm;vertical-align:top;}
+th {text-align:center !important;font-weight:normal}
+
+/* Текст в рамках */
+#preview .iz::after {content: "Изм.";}
+#preview .ls::after,
+#preview .pl::after {content: "Лист";}
+#preview .nd::after {content: "№ докум.";}
+#preview .pd::after {content: "Подпись";}
+#preview .dt::after {content: "Дата";}
+#preview .rz::after {content: "Разраб.";}
+#preview .lt::after {content: "Лит.";}
+#preview .al::after {content: "Листов";}
+#preview .pr::after {content: "Провер.";}
+#preview .nc::after {content: "Н. Контр.";}
+#preview .ut::after {content: "Утверд.";}
+
+#preview .page.big .co {left:9.2cm; bottom: 3.5cm; right:0.6cm;font-size:22px;text-align:center;}
+#preview .page.big .iz {left:2.1cm;bottom:3.1cm;}
+#preview .page.big .ls {left:3.15cm;bottom:3.1cm;}
+#preview .page.big .nd {left:4.7cm;bottom:3.1cm;}
+#preview .page.big .pd {left:6.6cm;bottom:3.1cm;}
+#preview .page.big .dt {left:8.24cm;bottom:3.1cm;}
+#preview .page.big .rz {left:2.15cm;bottom:2.6cm;}
+#preview .page.big .sr {left:4.13cm; bottom:2.6cm;}
+#preview .page.big .lt {left:15.6cm;bottom:2.6cm;}
+#preview .page.big .pl {left:17.1cm;bottom:2.6cm;}
+#preview .page.big .al {left:18.8cm;bottom:2.6cm;}
+#preview .page.big .pr {left:2.15cm;bottom:2.1cm;}
+#preview .page.big .st {left:4.13cm; bottom:2.1cm;}
+#preview .page.big .cp {left:16.71cm;right:2.9cm;bottom:2.1cm;text-align:center;}
+#preview .page.big .pc {left:18.1cm;right:0.6cm;bottom:2.1cm;text-align:center;}
+#preview .page.big .nm {left:9.1cm;bottom:1.6cm;right:5.85cm;font-size:16px;text-align:center;}
+#preview .page.big .gr {left:15.25cm;right:0.6cm;bottom:1cm;text-align:center;font-size:20px;}
+#preview .page.big .nc {left:2.15cm;bottom:1.1cm;}
+#preview .page.big .ut {left:2.15cm;bottom:0.6cm;}
+
+#preview .page.small .iz {left:2.15cm;bottom:0.6cm;}
+#preview .page.small .ls {left:3.15cm;bottom:0.6cm;}
+#preview .page.small .nd {left:4.61cm;bottom:0.6cm;}
+#preview .page.small .pd {left:6.6cm;bottom:0.6cm;}
+#preview .page.small .dt {left:8.25cm;bottom:0.6cm;}
+#preview .page.small .co {left:9.1cm;bottom:1.0cm;right:1.5cm;font-size:22px;text-align:center;}
+#preview .page.small .pl {left:19.55cm;bottom:1.55cm;}
+#preview .page.small .cp {left:19.5cm;bottom:0.90cm;right:0.5cm;text-align:center;}
+
+.opt {display:block;text-align:center !important;font-size:18px;color:white;font-family:sans;margin: 8px 0px;user-select:none;}
+.opt:hover {color:#005BFF;}
+
+@media print {
+	nav,
+	#markuparea,
+	button,
+	#preview hr,
+	#control-buttons,
+	.no-print {
+		display: none;
+	}
+	
+	#controls {
+		visibility: hidden;
+		width: auto;
+		margin: 0;
+		padding: 0;
+		max-width: unset;
+		border: none;
+		border-radius: 0px;
+	}
+
+	#preview {
+		display: block !important;
+		visibility: visible;
+	}
+
+	#preview .page-container {
+		padding: 0;
+	}
+
+	#preview .page {
+		background-color: white;
+		color: black;
+	}
 }

+ 39 - 39
src/index.php

@@ -1,39 +1,39 @@
-<?php
-// Главный файл
-
-require_once "config.php";
-require_once "pockit.php";
-
-// Автозагрузка
-spl_autoload_register("Pockit::autoload");
-
-// Определение маршрутов
-$router = new Router();
-
-// Главная
-$router->register('', 'HomeController::index');
-
-// Оценки
-$router->register('/grades', 'GradesController::index');
-$router->register('/grades/get', 'GradesController::collect');
-
-// Regen
-$router->register('/regen/new', 'RegenController::newReport');
-$router->register('/regen/upload-image', 'RegenController::uploadImage');
-$router->register('/regen/edit/{report_id}', 'RegenController::edit');
-$router->register("/regen/gethtml", 'RegenController::getHtml');
-$router->register("/regen/archive", 'RegenController::archive');
-$router->register("/regen/archive/{subject_id}", 'RegenController::listReports');
-
-// API
-$router->register('/subjects/create', 'ApiController::createSubject');
-$router->register('/subjects/update', 'ApiController::updateSubject');
-$router->register('/subjects/delete', 'ApiController::deleteSubject');
-$router->register('/reports/update', 'ApiController::updateReport');
-$router->register('/reports/delete', 'ApiController::deleteReport');
-$router->register('/teachers/read', 'ApiController::getTeachers');
-$router->register('/work_types/read', 'ApiController::getWorkTypes');
-
-$router->register404('NotFoundController::index');
-
-return $router->handle($_SERVER['REQUEST_URI']);
+<?php
+// Главный файл
+
+require_once "config.php";
+require_once "pockit.php";
+
+// Автозагрузка
+spl_autoload_register("Pockit::autoload");
+
+// Определение маршрутов
+$router = new Router();
+
+// Главная
+$router->register('', 'HomeController::index');
+
+// Оценки
+$router->register('/grades', 'GradesController::index');
+$router->register('/grades/get', 'GradesController::collect');
+
+// Regen
+$router->register('/regen/new', 'RegenController::newReport');
+$router->register('/regen/upload-image', 'RegenController::uploadImage');
+$router->register('/regen/edit/{report_id}', 'RegenController::edit');
+$router->register("/regen/gethtml", 'RegenController::getHtml');
+$router->register("/regen/archive", 'RegenController::archive');
+$router->register("/regen/archive/{subject_id}", 'RegenController::listReports');
+
+// API
+$router->register('/subjects/create', 'ApiController::createSubject');
+$router->register('/subjects/update', 'ApiController::updateSubject');
+$router->register('/subjects/delete', 'ApiController::deleteSubject');
+$router->register('/reports/update', 'ApiController::updateReport');
+$router->register('/reports/delete', 'ApiController::deleteReport');
+$router->register('/teachers/read', 'ApiController::getTeachers');
+$router->register('/work_types/read', 'ApiController::getWorkTypes');
+
+$router->register404('NotFoundController::index');
+
+return $router->handle($_SERVER['REQUEST_URI']);

+ 122 - 122
src/js/pockit.js

@@ -1,123 +1,123 @@
-// Возвращает список значений из БД
-async function crudRead(route, limit = 999) {
-	const url = "/"+route+"/read?limit="+limit;
-	return $.ajax({
-		type: 'POST',
-		url: url
-	});
-}
-
-// Отправляет запрос к API на удаление
-// После удаления на странице удаляется элемент с ID контейнера
-function crudDelete(route, id, containerID=null) {
-	if (!confirm("Точно удалить?")) {
-		return;
-	}
-	const url = '/'+route+'/delete?id='+id;
-	$.ajax({
-		url: url,
-		success: function() {
-			document.getElementById(containerID).remove();
-		}
-	});
-}
-
-// Показывает форму обновления элемента
-async function crudUpdateShowWindow(route, options, name, afterUpdatedCallback) {
-	const card = await createWindow(route, "update", name, options, afterUpdatedCallback)
-    $(document.body).append(card);
-	$(document.body).append($('<div class="dark-overlay"></div>'));
-}
-
-// Показывает форму создания элемента
-async function crudCreateShowWindow(route, options, name, afterCreatedCallback) {
-	const card = await createWindow(route, "create", name, options, afterCreatedCallback)
-    $(document.body).append(card);
-	$(document.body).append($('<div class="dark-overlay"></div>'));
-}
-
-async function createWindow(route, action, name, options, afterCallback) {
-	// Создание карточки-контейнера
-	const card = $("<div class='card modal'></div>");
-
-	// Создание формы и привязка к ней обратного вызова
-	const form = $("<form class='crudcreateform' method='post' action='/"+route+"/"+action+"'></form>");
-	form.submit(function(e) {
-		e.preventDefault();
-		$.ajax({
-			url: "/"+route+"/"+action,
-			type: 'post',
-			data: $(this).serialize(),
-			success: function(response) {
-				// Окно и слой затемнения удаляется
-				removeModalWindows();
-				// Вызывается функция обратного вызова с параметром - объектом с информацией о созданном элементе
-				afterCallback(JSON.parse(response));
-			}
-		});
-	});
-
-	// Добавление к форме полей ввода
-	for (const [key, value] of Object.entries(options)) {
-
-		if (value.type == "hidden") {
-			// Невидимое поле ввода
-			form.append($('<input type="hidden" name="'+value.name+'" value="'+value.default+'"/>'));
-			continue;
-		}
-		
-		// Добавляем контейнер поля
-		let control_container = $("<div class='form-control-container'></div>");
-
-		// Надпись
-		control_container.append($('<label for="'+key+'">'+ key +'</label>'));
-
-		// Непосредственно поле ввода
-		if (value.type == 'plain')  {
-			// Текстовое
-			const input = $('<input class="form-control" id="'+key+'" type="text" name="'+value.name+'"/>');
-			if (value.default != undefined) {
-				input.attr("value", value.default);
-			}
-			control_container.append(input);
-			
-		} else if (value.type == 'crudRead') {
-			// Выбор из нескольких вариантов
-			
-			const values = JSON.parse(await crudRead(value.route));
-			const selectInput = $('<select class="form-control" id="'+key+'" name="'+value.name+'">');
-			for (let i = 0; i < values.length; ++i) {
-				if (values[i].id == value.default) {
-					// Это значение по-умолчанию
-					selectInput.append($('<option selected="selected" value="'+values[i].id+'">'+values[i].repr+'</option>'));
-				} else {
-					selectInput.append($('<option value="'+values[i].id+'">'+values[i].repr+'</option>'));
-				}
-			}
-			control_container.append(selectInput);
-
-		} else {
-			console.log("Неизвестный тип: " + value.type);
-		}
-
-		form.append(control_container);
-	};
-
-	// Кнопки добавления и отмены
-	form.append($(`
-	<div style='display: grid;grid-template-columns: auto 25%;grid-gap: 1em;width: 100%;'>
-		<button type="submit" class="crudcreate createbutton form-control">Сохранить</button>
-		<button type="submit" onclick='removeModalWindows()' class="crudcancel form-control">Отмена</button>
-	</div>`));
-
-	// Добавление всех элементов и показ
-    card.append($('<h1>'+name+'</h1>'));
-    card.append(form);
-
-    return card;
-}
-
-// Удаляет все компоненты с модальными окнами
-function removeModalWindows() {
-	$('.modal, .dark-overlay').remove();
+// Возвращает список значений из БД
+async function crudRead(route, limit = 999) {
+	const url = "/"+route+"/read?limit="+limit;
+	return $.ajax({
+		type: 'POST',
+		url: url
+	});
+}
+
+// Отправляет запрос к API на удаление
+// После удаления на странице удаляется элемент с ID контейнера
+function crudDelete(route, id, containerID=null) {
+	if (!confirm("Точно удалить?")) {
+		return;
+	}
+	const url = '/'+route+'/delete?id='+id;
+	$.ajax({
+		url: url,
+		success: function() {
+			document.getElementById(containerID).remove();
+		}
+	});
+}
+
+// Показывает форму обновления элемента
+async function crudUpdateShowWindow(route, options, name, afterUpdatedCallback) {
+	const card = await createWindow(route, "update", name, options, afterUpdatedCallback)
+    $(document.body).append(card);
+	$(document.body).append($('<div class="dark-overlay"></div>'));
+}
+
+// Показывает форму создания элемента
+async function crudCreateShowWindow(route, options, name, afterCreatedCallback) {
+	const card = await createWindow(route, "create", name, options, afterCreatedCallback)
+    $(document.body).append(card);
+	$(document.body).append($('<div class="dark-overlay"></div>'));
+}
+
+async function createWindow(route, action, name, options, afterCallback) {
+	// Создание карточки-контейнера
+	const card = $("<div class='card modal'></div>");
+
+	// Создание формы и привязка к ней обратного вызова
+	const form = $("<form class='crudcreateform' method='post' action='/"+route+"/"+action+"'></form>");
+	form.submit(function(e) {
+		e.preventDefault();
+		$.ajax({
+			url: "/"+route+"/"+action,
+			type: 'post',
+			data: $(this).serialize(),
+			success: function(response) {
+				// Окно и слой затемнения удаляется
+				removeModalWindows();
+				// Вызывается функция обратного вызова с параметром - объектом с информацией о созданном элементе
+				afterCallback(JSON.parse(response));
+			}
+		});
+	});
+
+	// Добавление к форме полей ввода
+	for (const [key, value] of Object.entries(options)) {
+
+		if (value.type == "hidden") {
+			// Невидимое поле ввода
+			form.append($('<input type="hidden" name="'+value.name+'" value="'+value.default+'"/>'));
+			continue;
+		}
+		
+		// Добавляем контейнер поля
+		let control_container = $("<div class='form-control-container'></div>");
+
+		// Надпись
+		control_container.append($('<label for="'+key+'">'+ key +'</label>'));
+
+		// Непосредственно поле ввода
+		if (value.type == 'plain')  {
+			// Текстовое
+			const input = $('<input class="form-control" id="'+key+'" type="text" name="'+value.name+'"/>');
+			if (value.default != undefined) {
+				input.attr("value", value.default);
+			}
+			control_container.append(input);
+			
+		} else if (value.type == 'crudRead') {
+			// Выбор из нескольких вариантов
+			
+			const values = JSON.parse(await crudRead(value.route));
+			const selectInput = $('<select class="form-control" id="'+key+'" name="'+value.name+'">');
+			for (let i = 0; i < values.length; ++i) {
+				if (values[i].id == value.default) {
+					// Это значение по-умолчанию
+					selectInput.append($('<option selected="selected" value="'+values[i].id+'">'+values[i].repr+'</option>'));
+				} else {
+					selectInput.append($('<option value="'+values[i].id+'">'+values[i].repr+'</option>'));
+				}
+			}
+			control_container.append(selectInput);
+
+		} else {
+			console.log("Неизвестный тип: " + value.type);
+		}
+
+		form.append(control_container);
+	};
+
+	// Кнопки добавления и отмены
+	form.append($(`
+	<div style='display: grid;grid-template-columns: auto 25%;grid-gap: 1em;width: 100%;'>
+		<button type="submit" class="crudcreate createbutton form-control">Сохранить</button>
+		<button type="submit" onclick='removeModalWindows()' class="crudcancel form-control">Отмена</button>
+	</div>`));
+
+	// Добавление всех элементов и показ
+    card.append($('<h1>'+name+'</h1>'));
+    card.append(form);
+
+    return card;
+}
+
+// Удаляет все компоненты с модальными окнами
+function removeModalWindows() {
+	$('.modal, .dark-overlay').remove();
 }

+ 113 - 113
src/js/regenpreview.js

@@ -1,114 +1,114 @@
-// Текущее состояние страницы
-// 0 - режим редактирования
-// 1 - режим просмотра превью
-var current_state = 0;
-var markup_area = $('#markuparea');
-var preview_area = $('#preview');
-var saveButton = $('#saveMarkupButton');
-
-$(document).ready(function() {
-	textAreaAdjust(document.getElementById("markuparea"));
-
-	$("#saveForm").submit(function(e) {
-		e.preventDefault();
-		saveMarkup();
-	});
-
-	// Сохранение на Ctrl+S
-	document.addEventListener("keydown", function(e) {
-	  if (e.keyCode === 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)) {
-		e.preventDefault();
-		saveMarkup();
-	  }
-	}, false);
-
-	// Вставка картинок на Ctrl+V
-	// https://stackoverflow.com/a/6338207
-	$("#markuparea").on('paste', function (e) {
-		let items = (e.clipboardData || e.originalEvent.clipboardData).items;
-		for (let index in items) {
-			let item = items[index];
-			if (item.kind === 'file') {
-
-				// Загрузка файла через jQuery AJAX
-				// https://stackoverflow.com/a/13333478
-				var fd = new FormData();
-				fd.append('file', item.getAsFile());
-
-				$.ajax({
-					url: "/regen/upload-image",
-					type: "post",
-					data: fd,
-					processData: false,
-					contentType: false,
-					success: function (response) {
-						response = JSON.parse(response);
-
-						if (response.ok) {
-							const line = "?"+response.filename+":"+"Подпись изображения";
-							markup_area.val(markup_area.val() + line);
-						}
-					}
-				});
-			}
-		}
-	});
-});
-
-$("#switchPreview").click(function() {
-	markup_area.hide();
-	preview_area.show();
-	updatePreview();
-	saveMarkup();
-});
-
-$("#switchMarkup").click(function() {
-	markup_area.show();
-	preview_area.hide();
-	saveMarkup();
-});
-
-$("#printReport").click(function() {
-	saveMarkup();
-	updatePreview(true);
-})
-
-// При печати текста заставляет textbox расширяться
-function textAreaAdjust(element) {
-	element.style.height = "1px";
-	element.style.height = (25+element.scrollHeight)+"px";
-}
-
-function updatePreview(then_print=false) {
-	const str = document.location.toString();
-	const slash_idx = str.lastIndexOf('/');
-	const report_id = str.substring(slash_idx + 1);
-	$.post(
-		"/regen/gethtml",
-		{
-			markup: markup_area.val(),
-			report_id: report_id
-		},
-		function (data, textStatus, xhr) {
-			$("#preview").html(data);
-			if (then_print) {
-				window.print();
-			}
-		}
-	);
-}
-
-// Сохраняет разметку для данного отчёта
-function saveMarkup() {
-	$.ajax({
-		url: "/reports/update",
-		type: "post",
-		data: {
-			id: $("#idInput").val(),
-			markup: $("#markuparea").val()
-		},
-		success: function() {
-			saveButton.blur();
-		}
-	});
+// Текущее состояние страницы
+// 0 - режим редактирования
+// 1 - режим просмотра превью
+var current_state = 0;
+var markup_area = $('#markuparea');
+var preview_area = $('#preview');
+var saveButton = $('#saveMarkupButton');
+
+$(document).ready(function() {
+	textAreaAdjust(document.getElementById("markuparea"));
+
+	$("#saveForm").submit(function(e) {
+		e.preventDefault();
+		saveMarkup();
+	});
+
+	// Сохранение на Ctrl+S
+	document.addEventListener("keydown", function(e) {
+	  if (e.keyCode === 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)) {
+		e.preventDefault();
+		saveMarkup();
+	  }
+	}, false);
+
+	// Вставка картинок на Ctrl+V
+	// https://stackoverflow.com/a/6338207
+	$("#markuparea").on('paste', function (e) {
+		let items = (e.clipboardData || e.originalEvent.clipboardData).items;
+		for (let index in items) {
+			let item = items[index];
+			if (item.kind === 'file') {
+
+				// Загрузка файла через jQuery AJAX
+				// https://stackoverflow.com/a/13333478
+				var fd = new FormData();
+				fd.append('file', item.getAsFile());
+
+				$.ajax({
+					url: "/regen/upload-image",
+					type: "post",
+					data: fd,
+					processData: false,
+					contentType: false,
+					success: function (response) {
+						response = JSON.parse(response);
+
+						if (response.ok) {
+							const line = "?"+response.filename+":"+"Подпись изображения";
+							markup_area.val(markup_area.val() + line);
+						}
+					}
+				});
+			}
+		}
+	});
+});
+
+$("#switchPreview").click(function() {
+	markup_area.hide();
+	preview_area.show();
+	updatePreview();
+	saveMarkup();
+});
+
+$("#switchMarkup").click(function() {
+	markup_area.show();
+	preview_area.hide();
+	saveMarkup();
+});
+
+$("#printReport").click(function() {
+	saveMarkup();
+	updatePreview(true);
+})
+
+// При печати текста заставляет textbox расширяться
+function textAreaAdjust(element) {
+	element.style.height = "1px";
+	element.style.height = (25+element.scrollHeight)+"px";
+}
+
+function updatePreview(then_print=false) {
+	const str = document.location.toString();
+	const slash_idx = str.lastIndexOf('/');
+	const report_id = str.substring(slash_idx + 1);
+	$.post(
+		"/regen/gethtml",
+		{
+			markup: markup_area.val(),
+			report_id: report_id
+		},
+		function (data, textStatus, xhr) {
+			$("#preview").html(data);
+			if (then_print) {
+				window.print();
+			}
+		}
+	);
+}
+
+// Сохраняет разметку для данного отчёта
+function saveMarkup() {
+	$.ajax({
+		url: "/reports/update",
+		type: "post",
+		data: {
+			id: $("#idInput").val(),
+			markup: $("#markuparea").val()
+		},
+		success: function() {
+			saveButton.blur();
+		}
+	});
 }

+ 87 - 87
src/pockit.php

@@ -1,87 +1,87 @@
-<?php
-
-// Функции, необходимые везде
-class Pockit {
-	public static function autoload($classname) {
-		if (preg_match('/Controller$/', $classname)) {
-			require_once rootdir.'/controllers/'.$classname.'.php';
-		} else if (preg_match('/View$/', $classname)) {
-			require_once rootdir.'/views/'.$classname.'.php';
-		} else if (preg_match('/Model$/', $classname)) {
-			require_once rootdir.'/models/'.$classname.'.php';
-		}
-	}
-}
-
-class Router {
-	private $routes = array();
-	private $not_found_handler;
-
-	// Регистрирует маршрут
-	public function register(string $uri, callable $handler) {
-		$this->routes[$uri] = $handler;
-	}
-
-	// Регистрирует маршрут для 404
-	public function register404(callable $handler) {
-		$this->not_found_handler = $handler;
-	}
-
-	// Выполняет маршрутизацию
-	public function handle(string $request_uri) {
-		if (preg_match('/^\/(?:css|fonts|img|jquery|js)\//', $request_uri)) {
-			// Подача как есть
-			return false;
-		}
-
-		// Ищем подходящий маршрут
-		// TODO: кэшировать regex-выражения если приложение не в режиме разработки
-		foreach ($this->routes as $route => $callback) {
-			// Преобразование маршрута в regex выражение
-			$pattern = preg_replace(
-				['/\//', '/{(\w+)}/'],
-				['\\\/', '(?<$1>\\w+)'],
-				$route
-			);
-			$pattern = '/^'.$pattern.'\/?((?:\?|\&)\w+=\w*)*$/';
-			if (preg_match($pattern, $request_uri, $named_groups)) {
-				// Поиск именованных групп в $named_groups
-				$named_groups = array_filter($named_groups, function($key) {
-					return !is_numeric($key);
-				}, ARRAY_FILTER_USE_KEY);
-				$this->invoke($callback, $named_groups);
-			}
-		}
-
-		// Маршрут не найден, вызываем 404!
-		header("HTTP/1.1 404 Not Found");
-		$this->invoke($this->not_found_handler, []);
-	}
-
-	// Вызывает функцию, которую определил маршрут
-	private function invoke(callable $callback, array $parameters) {
-		call_user_func_array($callback, $parameters);
-		exit();
-	}
-}
-
-// Класс работы с БД
-class Database {
-	private static $db;
-	private $connection;
-	
-	private function __construct() {
-		$this->connection = new SQLite3(rootdir.'/db.sqlite3');
-	}
-
-	function __destruct() {
-		$this->connection->close();
-	}
-
-	public static function getConnection() {
-		if (self::$db == null) {
-			self::$db = new Database();
-		}
-		return self::$db->connection;
-	}
-}
+<?php
+
+// Функции, необходимые везде
+class Pockit {
+	public static function autoload($classname) {
+		if (preg_match('/Controller$/', $classname)) {
+			require_once rootdir.'/controllers/'.$classname.'.php';
+		} else if (preg_match('/View$/', $classname)) {
+			require_once rootdir.'/views/'.$classname.'.php';
+		} else if (preg_match('/Model$/', $classname)) {
+			require_once rootdir.'/models/'.$classname.'.php';
+		}
+	}
+}
+
+class Router {
+	private $routes = array();
+	private $not_found_handler;
+
+	// Регистрирует маршрут
+	public function register(string $uri, callable $handler) {
+		$this->routes[$uri] = $handler;
+	}
+
+	// Регистрирует маршрут для 404
+	public function register404(callable $handler) {
+		$this->not_found_handler = $handler;
+	}
+
+	// Выполняет маршрутизацию
+	public function handle(string $request_uri) {
+		if (preg_match('/^\/(?:css|fonts|img|jquery|js)\//', $request_uri)) {
+			// Подача как есть
+			return false;
+		}
+
+		// Ищем подходящий маршрут
+		// TODO: кэшировать regex-выражения если приложение не в режиме разработки
+		foreach ($this->routes as $route => $callback) {
+			// Преобразование маршрута в regex выражение
+			$pattern = preg_replace(
+				['/\//', '/{(\w+)}/'],
+				['\\\/', '(?<$1>\\w+)'],
+				$route
+			);
+			$pattern = '/^'.$pattern.'\/?((?:\?|\&)\w+=\w*)*$/';
+			if (preg_match($pattern, $request_uri, $named_groups)) {
+				// Поиск именованных групп в $named_groups
+				$named_groups = array_filter($named_groups, function($key) {
+					return !is_numeric($key);
+				}, ARRAY_FILTER_USE_KEY);
+				$this->invoke($callback, $named_groups);
+			}
+		}
+
+		// Маршрут не найден, вызываем 404!
+		header("HTTP/1.1 404 Not Found");
+		$this->invoke($this->not_found_handler, []);
+	}
+
+	// Вызывает функцию, которую определил маршрут
+	private function invoke(callable $callback, array $parameters) {
+		call_user_func_array($callback, $parameters);
+		exit();
+	}
+}
+
+// Класс работы с БД
+class Database {
+	private static $db;
+	private $connection;
+	
+	private function __construct() {
+		$this->connection = new SQLite3(rootdir.'/db.sqlite3');
+	}
+
+	function __destruct() {
+		$this->connection->close();
+	}
+
+	public static function getConnection() {
+		if (self::$db == null) {
+			self::$db = new Database();
+		}
+		return self::$db->connection;
+	}
+}

+ 2 - 2
src/tools/database_up.php

@@ -1,3 +1,3 @@
-<?php
-
+<?php
+
 $db = new SQLite3(__DIR__."/../database.sqlite3");

+ 48 - 48
src/views/LayoutView.php

@@ -1,48 +1,48 @@
-<?php
-// Класс разметки
-
-class LayoutView extends View {
-	protected $page_title;
-	protected $crumbs = array();
-
-	protected function breadcrumbs() { ?>
-<nav class="breadcrumb">
-	<ul>
-	<?php foreach ($this->crumbs as $crumb => $url) {
-		if ($crumb === array_key_last($this->crumbs)) { ?>
-			<li class="breadcrumb-item active"><?= $crumb ?></li>
-		<?php } else { ?>
-			<li class="breadcrumb-item"><a href="<?= $url ?>"><?= $crumb ?></a></li>
-		<?php } ?>
-	<?php } ?>
-	</ul>
-</nav>
-<?php }
-
-	protected function customHead() {
-		
-	}
-	
-	public function view():void { ?>
-
-<!DOCTYPE html>
-<html lang="ru">
-	<head>
-		<meta charset="UTF-8">
-		<meta name="viewport" content="width=device-width, initial-scale=1.0">
-		<meta http-equiv="Cache-control" content="public">
-		<title><?= $this->page_title ?></title>
-		<link rel='icon' type='image/png' href='/img/favicons/pockit.png'>
-		<link rel="stylesheet" href="/css/pockit.css">
-		<script src="/jquery/jquery.min.js"></script>
-		<script src="/js/pockit.js"></script>
-		<?php $this->customHead() ?>
-	</head>
-	<body>
-		<?php $this->breadcrumbs(); ?>
-		<?php $this->content(); ?>
-	</body>
-</html>
-
-<?php }
-}
+<?php
+// Класс разметки
+
+class LayoutView extends View {
+	protected $page_title;
+	protected $crumbs = array();
+
+	protected function breadcrumbs() { ?>
+<nav class="breadcrumb">
+	<ul>
+	<?php foreach ($this->crumbs as $crumb => $url) {
+		if ($crumb === array_key_last($this->crumbs)) { ?>
+			<li class="breadcrumb-item active"><?= $crumb ?></li>
+		<?php } else { ?>
+			<li class="breadcrumb-item"><a href="<?= $url ?>"><?= $crumb ?></a></li>
+		<?php } ?>
+	<?php } ?>
+	</ul>
+</nav>
+<?php }
+
+	protected function customHead() {
+		
+	}
+	
+	public function view():void { ?>
+
+<!DOCTYPE html>
+<html lang="ru">
+	<head>
+		<meta charset="UTF-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1.0">
+		<meta http-equiv="Cache-control" content="public">
+		<title><?= $this->page_title ?></title>
+		<link rel='icon' type='image/png' href='/img/favicons/pockit.png'>
+		<link rel="stylesheet" href="/css/pockit.css">
+		<script src="/jquery/jquery.min.js"></script>
+		<script src="/js/pockit.js"></script>
+		<?php $this->customHead() ?>
+	</head>
+	<body>
+		<?php $this->breadcrumbs(); ?>
+		<?php $this->content(); ?>
+	</body>
+</html>
+
+<?php }
+}

File diff suppressed because it is too large
+ 0 - 90
src/views/RegenPageView.php


+ 1 - 1
start.sh

@@ -1 +1 @@
-php -S 127.0.0.1:9000 -t ./src ./src/index.php
+php -S 127.0.0.1:9000 -t ./src ./src/index.php

+ 91 - 91
tools/setup.php

@@ -1,91 +1,91 @@
-<?php
-
-define("COLOR_DEFAULT", "");
-define("COLOR_YELLOW", "\033[93m");
-define("COLOR_RED", "\033[91m");
-define("COLOR_GREEN", "\033[92m");
-define("COLOR_TERMINATOR", "\033[0m");
-
-// Выводит строку информации и окрашивает её в определённый цвет
-function displayMessage($string, $color = COLOR_DEFAULT) : void {
-	echo $color.$string.COLOR_TERMINATOR;
-}
-
-// Создаёт БД
-function databaseUp($file_path) {
-	$db = new SQLite3($file_path);
-	$db->query('CREATE TABLE "regen_reports" (
-	"id"	INTEGER,
-	"subject_id"	INTEGER,
-	"work_type"	INTEGER,
-	"work_number"	TEXT,
-	"notice"	TEXT,
-	"date_create"	DATETIME,
-	"markup"	TEXT,
-	PRIMARY KEY("id"))');
-
-	$db->query('CREATE TABLE "regen_subjects"(
-	"id" INTEGER PRIMARY KEY,
-	"name" TEXT,
-	"code" TEXT,
-	"teacher_id" INTEGER)');
-
-	$db->query('CREATE TABLE "regen_teachers" (
-	"id" INTEGER PRIMARY KEY,
-	"surname" TEXT,
-	"name" TEXT,
-	"patronymic" TEXT)');
-
-	$db->query('CREATE TABLE "regen_worktypes"(
-	"id" INTEGER PRIMARY KEY,
-	"name_nom" TEXT,
-	"name_gen" TEXT,
-	"name_titlepage" INTEGER)');
-
-	$db->query("INSERT INTO 'regen_teachers' VALUES (1,'Пивоваров','Сергей','Александрович')");
-	$db->query("INSERT INTO 'regen_teachers' VALUES (2,'Ильина','Светлана','Анатольевна')");
-	$db->query("INSERT INTO 'regen_teachers' VALUES (3,'Галимова','Екатерина','Валерьевна')");
-	$db->query("INSERT INTO 'regen_teachers' VALUES (4,'Немтинова','Елена','Александровна')");
-	$db->query("INSERT INTO 'regen_worktypes' VALUES (1,'ЛАБОРАТОРНАЯ РАБОТА','ЛАБОРАТОРНОЙ РАБОТЫ','лабораторной работе')");
-	$db->query("INSERT INTO 'regen_worktypes' VALUES (2,'ПРАКТИЧЕСКАЯ РАБОТА','ПРАКТИЧЕСКОЙ РАБОТЫ','практической работе')");
-}
-
-// Возвращает ввод пользователя
-function userInput($prompt) : string {
-	displayMessage($prompt, COLOR_DEFAULT);
-	return rtrim(fgets(STDIN));
-}
-
-// 1. Создание БД
-$db_path = __DIR__."/../src/db.sqlite3";
-if (file_exists($db_path)) {
-	displayMessage("Файл базы данных уже существует -- пропускаем создание\n", COLOR_YELLOW);
-} else {
-	displayMessage("Создаём базу данных...\n", COLOR_YELLOW);
-	databaseUp($db_path);
-	displayMessage("База данных успешно создана!\n", COLOR_GREEN);
-}
-
-// 2. Заполнение .env файла
-$env_path = __DIR__."/../src/.env";
-if (file_exists($env_path)) {
-
-	displayMessage("Файл .env уже существует -- пропускаем создание\n", COLOR_YELLOW);
-
-} else {
-
-	$user_name = userInput("Как тебя зовут?\n");
-	$journal_login = userInput("Введи логин от электронного дневника\n");
-	$journal_password = userInput("Введи пароль от электронного дневника\n");
-	$period_id = userInput("Введи текущий period_id\n");
-
-	$fp = fopen($env_path, 'w');
-	fwrite($fp, 'user_name="'.$user_name.'"'."\n");
-	fwrite($fp, 'journal_login="'.$journal_login.'"'."\n");
-	fwrite($fp, 'journal_password="'.$journal_password.'"'."\n");
-	fwrite($fp, 'period_id="'.$period_id.'"'."\n");
-	fclose($fp);
-
-	displayMessage("Файл настроек успешно создан!\n", COLOR_GREEN);
-
-}
+<?php
+
+define("COLOR_DEFAULT", "");
+define("COLOR_YELLOW", "\033[93m");
+define("COLOR_RED", "\033[91m");
+define("COLOR_GREEN", "\033[92m");
+define("COLOR_TERMINATOR", "\033[0m");
+
+// Выводит строку информации и окрашивает её в определённый цвет
+function displayMessage($string, $color = COLOR_DEFAULT) : void {
+	echo $color.$string.COLOR_TERMINATOR;
+}
+
+// Создаёт БД
+function databaseUp($file_path) {
+	$db = new SQLite3($file_path);
+	$db->query('CREATE TABLE "regen_reports" (
+	"id"	INTEGER,
+	"subject_id"	INTEGER,
+	"work_type"	INTEGER,
+	"work_number"	TEXT,
+	"notice"	TEXT,
+	"date_create"	DATETIME,
+	"markup"	TEXT,
+	PRIMARY KEY("id"))');
+
+	$db->query('CREATE TABLE "regen_subjects"(
+	"id" INTEGER PRIMARY KEY,
+	"name" TEXT,
+	"code" TEXT,
+	"teacher_id" INTEGER)');
+
+	$db->query('CREATE TABLE "regen_teachers" (
+	"id" INTEGER PRIMARY KEY,
+	"surname" TEXT,
+	"name" TEXT,
+	"patronymic" TEXT)');
+
+	$db->query('CREATE TABLE "regen_worktypes"(
+	"id" INTEGER PRIMARY KEY,
+	"name_nom" TEXT,
+	"name_gen" TEXT,
+	"name_titlepage" INTEGER)');
+
+	$db->query("INSERT INTO 'regen_teachers' VALUES (1,'Пивоваров','Сергей','Александрович')");
+	$db->query("INSERT INTO 'regen_teachers' VALUES (2,'Ильина','Светлана','Анатольевна')");
+	$db->query("INSERT INTO 'regen_teachers' VALUES (3,'Галимова','Екатерина','Валерьевна')");
+	$db->query("INSERT INTO 'regen_teachers' VALUES (4,'Немтинова','Елена','Александровна')");
+	$db->query("INSERT INTO 'regen_worktypes' VALUES (1,'ЛАБОРАТОРНАЯ РАБОТА','ЛАБОРАТОРНОЙ РАБОТЫ','лабораторной работе')");
+	$db->query("INSERT INTO 'regen_worktypes' VALUES (2,'ПРАКТИЧЕСКАЯ РАБОТА','ПРАКТИЧЕСКОЙ РАБОТЫ','практической работе')");
+}
+
+// Возвращает ввод пользователя
+function userInput($prompt) : string {
+	displayMessage($prompt, COLOR_DEFAULT);
+	return rtrim(fgets(STDIN));
+}
+
+// 1. Создание БД
+$db_path = __DIR__."/../src/db.sqlite3";
+if (file_exists($db_path)) {
+	displayMessage("Файл базы данных уже существует -- пропускаем создание\n", COLOR_YELLOW);
+} else {
+	displayMessage("Создаём базу данных...\n", COLOR_YELLOW);
+	databaseUp($db_path);
+	displayMessage("База данных успешно создана!\n", COLOR_GREEN);
+}
+
+// 2. Заполнение .env файла
+$env_path = __DIR__."/../src/.env";
+if (file_exists($env_path)) {
+
+	displayMessage("Файл .env уже существует -- пропускаем создание\n", COLOR_YELLOW);
+
+} else {
+
+	$user_name = userInput("Как тебя зовут?\n");
+	$journal_login = userInput("Введи логин от электронного дневника\n");
+	$journal_password = userInput("Введи пароль от электронного дневника\n");
+	$period_id = userInput("Введи текущий period_id\n");
+
+	$fp = fopen($env_path, 'w');
+	fwrite($fp, 'user_name="'.$user_name.'"'."\n");
+	fwrite($fp, 'journal_login="'.$journal_login.'"'."\n");
+	fwrite($fp, 'journal_password="'.$journal_password.'"'."\n");
+	fwrite($fp, 'period_id="'.$period_id.'"'."\n");
+	fclose($fp);
+
+	displayMessage("Файл настроек успешно создан!\n", COLOR_GREEN);
+
+}

Some files were not shown because too many files changed in this diff