Benutzer-Werkzeuge

Webseiten-Werkzeuge


de.bkp:intern:ipa:pg2017:ipa_pg2017
Wochenberichtsmodul RIO
IPA Bericht
Zürich, Thursday, 28. January 2021
Philipp Gabathuler





Teil 1Umfeld und Ablauf

Das Projekt und sein Umfeld werden beschrieben. Im Projekt sollten alle in der Aufgabenstellung erwähnten Anforderung umgesetzt werden. Die beschriebene Projektmethode und der Zeitplan sollen den Projektablauf strukturieren.

1.1.Ausgangslage

Die Rafisa Informatik braucht ein Modul für das interne RIO-System wo man Wochenberichte erfassen und editieren kann.

Diese Wochenberichte sollen Personen zugewiesen werden können. Wochenberichte sollen Tageseinträge haben, die man täglich ausfüllen kann mit den Arbeiten, die man an diesem Tag getätigt hat. Die Wochenberichte sollen signiert werden können, vom Fachvorgesetzten und vom Benutzer.

Der Wochenbericht umfasst fünf Tage und mittels des RIO-Kalender-Controls soll ersichtlich sein, welche Tage schon erfasst wurden.

1.2.Detaillierte Aufgabenstellung

Der erste Schritt ist das Sicherstellen, dass Sie die notwendige Software und Hardware installiert und konfiguriert haben,
damit Sie professionell damit entwickeln können.

Das Projekt wird von Ihnen mit der Netbeans Entwicklungsumgebung initialisiert und entwickelt.
Damit eine Kontrolle und Nachverfolgung möglich ist, benutzen Sie das RIO-Mercurial-Repository.

Damit eine Kontrolle und Nachverfolgung möglich ist, erstellen Sie ein Mercurial Repository auf dem externen Rafisa Root Server. Die Commits sollen im Umfang möglichst klein sein und themenverwandt. Die Commits müssen in guter Qualität kommentiert werden. Jeden Abend soll ein Push auf den Server erfolgen.

  • Initialisierung des „Repository“ auf dem Mercurial-Server.
  • Kleine Commits, z.B. Beim Arbeitsjournal wurde einen Tageseintrag fertiggestellt.
  • Themenverwandte Commits, z.B. Business-Klassen wurden erweitert.
  • Täglicher Push auf den Server.


Funktion / Programmierung:

Das Modul soll Objektorientiert aufgebaut werden und folgende Richtlinien sind einzuhalten:

  • Benutzung der Entity-Infrastruktur.
  • Benutzung der RIODatabase-Persistenz-Infrastruktur.
  • Benutzung der RIOValidate-Infrastruktur.
  • Benutzung der RIOWidget-Infrastruktur.
  • Benutzung der RIOAction-Infrastruktur.



Code-Konventionen:

  • Namenskonvention: analog Java für selbstdefinierte Programmteile. Klassen sind ‚upper camel case‘, Methoden sind ‚lower camel case‘, Variablen sind ‚lower camel case‘ und Konstanten müssen ‚upper case underscore separated‘ sein.
  • Formatierung des Codes: Netbeans PHP Standard Formatierer.
  • Quellcode Dokumentation, Klassen Definition, ausführliche Kommentare inklusive Instanziierungsbeispiele. Methoden ausführlich inklusive Parameter, Rückgabewerte und etwaige Throws.
  • Variablen/Felder durch das ‚protected‘ Schlüsselwort geschützt, Zugriff auf diese nur durch Methoden.
  • Stringliterale wo möglich und sinnvoll als Konstanten.
  • Meldungen und Beschriftungen durch externe Dateien.





Logging:

Es wird die bestehende Log-Infrastruktur benutzt werden.

Jede Aktion soll in einer Logdatei festgehalten werden. Aus dieser Datei soll ersichtlich sein wer, was, wann getätigt hat.

  • Jede Aktion eines Benutzers wird festgehalten.
  • Jeder Eintrag hat einen Zeitstempel.
  • Jeder Eintrag hat den Aktionsnamen der durchgeführt wurde.
  • Jeder Eintrag hat die ID des Benutzers der die Aktion durchgeführt hat.



Dokumentation:

Um den Aufbau für den Auftraggeber möglichst transparent zu machen, erstellen Sie die pertinenten Use-Cases, UML 2.0 und ERM-Diagramme, aus welchen folgende Angaben ableitbar sind:

  • Aufbau des Wochenberichtmoduls.
  • Einbindung in das bestehende System.


Quellcode Dokumentation:

  • Klassen Definition, ausführliche Kommentare inklusive Instanziierungsbeispiele.
  • Methoden, ausführlich inklusive Parameter, Rückgabewerte und etwaige Throws.


Folgende Technologien werden vom Auftraggeber vorgegeben:

  • PHP OOP
  • JavaScript
  • jQuery
  • HTML5
  • CSS
  • MySQL



Die RIO-Applikation ist mit der Multi-Tier Architektur implementiert, folgende Ebenen sind zu berücksichtigen:

  • Präsentation
  • Aktionen
  • Logik/Business
  • Persistenz



Wochenbericht

Der Wochenbericht dient dazu, dass ein Benutzer seine tägliche Arbeit und damit verbundene Erfahrungen digital festhalten kann. Ein Wochenbericht besteht aus 5 Arbeitsjournalen, die den Arbeits-Wochentage zugeordnet sind. Der Benutzer soll das für den Tag aktuelle Arbeitsjournal ausfüllen. Im Arbeitsjournal soll der Benutzer seine durch den Tag erledigte Arbeiten und Reflexionen festhalten. Wichtig ist, dass das Formular übersichtlich und klar strukturiert ist. Der Benutzer soll darauf hingewiesen werden, wenn ein Arbeitsformular nicht ausgefüllt wurde. Am Ende der Arbeitswoche soll der Wochenbericht signiert werden. Durch das Signieren kann der Wochenbericht nicht mehr geändert werden und die Arbeitsjournale sind gesperrt.

Arbeitsjournal

Ein Arbeitsjournal ist in verschiedenen Sektionen unterteilt, die wie folgt definiert sind:

  • Tagesziel
  • Arbeitszeit
  • Tasks / Aufgaben
  • Bemerkungen
  • Nächste Arbeitsschritte



Im Tagesziel beschreibt der Benutzer die Ziele für die Tagesarbeiten.

Bei der Arbeitszeit gibt der Benutzer die aufgewendete Zeit für die verschiedenen Tagesarbeiten an.

Die Tasks / Aufgaben sind wie folgend aufgebaut:

  • Zeit
  • Erledigte Aufgaben
  • Erfolge
  • Schwierigkeiten


Die Zeit gibt an, der Zeitraum der der Benutzer für diese Arbeit aufgewendet hat.

Die erledigten Aufgaben sollen eine kurze Beschreibung der im Zeitraum getätigten Arbeit sein.

Bei den Erfolgen kann der Benutzer, z.B., neue Strategien kurz beschreiben die er bei der Erledigung der Arbeit erlernt hat.

Bei den Schwierigkeiten kann der Benutzer Probleme die bei der Arbeit sich herauskristallisierten beschreiben, z.B. wenn mehr Zeit als eingeplant aufgewendet wurde.

Die Bemerkungen sollen eine Reflexion des Benutzers über die Tagesarbeiten sein.

Die nächsten Arbeitsschritte beschreiben die Arbeiten die der Benutzer am nächsten Arbeitstag in Angriff nehmen möchte.

Wochenbericht-Initialisierung:

Jede Kalenderwoche soll ein neuer Wochenbericht welcher für die aktuelle Arbeitswoche gültig ist initialisiert werden.
Die Initialisierung soll beim ersten Login einer Arbeitswoche des Benutzers erfolgen.

Wochenbericht; Bedienung:

Bei der Personenansicht im RIO-System soll neben dem „Performance“-Tab ein neuer Tab erscheinen mit der Beschriftung „Wochenbericht“. In dieser Tab-Ansicht soll mittels des RIOCalendarWidget die Woche angezeigt werden. Einen Doppelklick auf einen Tag soll ein Fenster erscheinen wo ein leeres Arbeitsjournal-Formular angezeigt wird, sofern der Benutzer für diesen Tag noch keine Daten eingegeben hat. Sollten schon Daten vorhanden sein, dann wird das Arbeitsjournal-Formular mit diesen Daten vorgefüllt angezeigt.

Das Arbeitsjournal-Formular darf nur bei unsignierten Wochenberichten editierbar sein.

Das Fenster welches das Arbeitsjournal-Formular beinhaltet, muss einen Knopf haben mit dem man das angezeigte Arbeitsjournal speichern kann, sofern dieses editierbar ist, also noch nicht signiert wurde.

Ein Wochenbericht kann nur signiert werden, wenn alle Arbeitsjournale der betroffenen Woche komplett ausgefüllt wurden.

Arbeitsjournal-Formular; Aufbau:

Dar Arbeitsjournal-Formular ist wie folgend aufgebaut:

  • Titel „Arbeitsjournal“ mit dem Datum des Tages welcher gerade editiert/angezeigt wird,
    z.B.: „Arbeitsjournal vom Donnerstag, 3 Juli 2017“
  • Die Sektion „Tagesziel“, ist ein Textfeld mit maximal 7 Zeilen und maximal 80 Zeichen pro Zeile.
  • Die Sektion „Arbeitszeit“ hat 2 Felder, bei einem Feld gibt man die Stunden ein, bei dem anderen die Minuten.
  • Die Sektion „Tasks / Aufgaben“ ist eine Tabelle mit den Spalten Zeit, Erledigte Aufgaben, Erfolge und Schwierigkeiten.



Die Spalte Zeit beinhaltet 2 Felder, im ersten Feld kann man die Stunden und im zweiten die Minuten eingeben.

Die Spalte Erledigte Aufgaben ist ein Textfeld.

Die Spalte Erfolge ist ein Textfeld.

Die Spalte Schwierigkeiten ist ein Textfeld.

  • Die Sektion „Bemerkungen“ ist ein Textfeld mit maximal 3 Zeilen und maximal 80 Zeichen pro Zeile.
  • Die Sektion „Nächste Arbeitsschritte“ ist ein Textfeld mit maximal 7 Zeilen mit maximal 80 Zeichen pro Zeile.
  • Jede Sektion ist mit dem Titel und einer Linie zu unterteilen, darunter sind die Eingabefelder zu platzieren.



Validierung; Wochenbericht:

  • Ein Wochenbericht kann Signiert/Abgeschlossen werden, wenn die Arbeitsjournale korrekt ausgefüllt wurden.
  • Ein Wochenbericht der abgeschlossen wurde ist nicht mehr editierbar.



Validierung; Arbeitsjournal:

  • Die Sektion „Tagesziel“ muss mindestens 40 Zeichen und maximal 560 Zeichen lang sein.
  • Die Sektion „Arbeitszeit“: bei den Stunden sind Ganzahlen von 1 bis 12 erlaubt, bei den Minuten von 0 bis 59.
  • Die Sektion „Tasks / Aufgaben“ ist eine Tabelle die maximal 7 Zeilen haben darf.



Bei der Spaten Zeit dürfen die der Stunden sind Ganzahlen von 1 bis 12 und bei den Minuten von 0 bis 59 sein.

Bei der Spalte „erledigte Aufgaben“ ist ein Text der minimal 15 Zeichen und maximal 120 Zeichen lang ist.

Bei der Spalte „Erfolge“ ist ein Text der minimal 0 Zeichen und maximal 240 Zeichen lang ist.

Bei der Spalte „Schwierigkeiten“ ist ein Text der minimal 0 Zeichen und maximal 240 Zeichen lang ist.

  • Die Sektion „Bemerkungen“ ist ein Text der minimal 0 Zeichen und maximal 560 Zeichen lang sein muss.
  • Die Sektion „Nächste Arbeitsschritte“ ist ein Text der minimal 15 Zeichen und maximal 560 Zeichen lang sein muss.



Meldungen an den Benutzer:

Jeden Abend, beim Ausloggen, soll überprüft werden, ob der Benutzer das Arbeitsjournal nachgetragen hat, sollte dies nicht der Fall sein, muss vor dem eigentlichen Ausloggen noch eine Meldung erscheinen die darauf hinweist.

Beim Einloggen soll überprüft werden ob Wochenberichte noch nicht ausgefüllt wurden. Sollten leere Arbeitsjournale vorhanden sein, erfolgt eine Meldung auf dem Bildschirm die auf diesen Zustand hinweisen. Die Meldung soll einen Knopf beinhalten mit welchem man in die Wochenbericht-Ansicht, „Wochenbericht-Tab“ gelangt.

Am Ende einer Arbeitswoche soll eine Meldung erscheinen die auf das Signieren hinweist.

1.3.Projektaufbauorganisation

Folgende Tabelle zeigt die Rollen, Verantwortlichkeiten und Kontaktdaten der an dem Projekt beteiligten Personen auf.

Rolle Name Aufgaben & Verantwortung Kontaktdaten/Adresse
Fachvorgesetzter Jorge Windmeisser Aufgabenstellung formulieren, Fachliche Richtigkeit der Arbeit beurteilen j.windmeisser@rafisa.ch G: +41 44 533 36 49
Berufsbildner Thomas Schärer Hauptverantwortlicher für die Ausbildung thomas.schaerer@gmx.net G: +41 44 533 36 42 N: +41 79 406 58 92
Experte Jean-Dominique Amsler Festlegung der Termine (Besuche, Präsentation und Fachgespräch) Bei auftretenden Problemen über Massnahmen entscheiden Arbeit beurteilen Qualität der Beurteilung sicherstellen amsler@iprolink.ch G: +41 44 454 11 44
Zweit-Experte Martin Achermann Arbeit beurteilen Qualität der Beurteilung sicherstellen Achermannma@bluewin.ch G: +41 79 423 17 85
Validexperte Kevin Cina Aufgabe validieren kevin@cinas.ch N: +41 78 672 84 56 
Kandidat Philipp Gabathuler Planung und Abwicklung des Projektes philiga02@gmail.com N: +41 79 277 39 25 Zimmer: 302
Lehrbetrieb &
Prüfungsort
Rafisa Informatik GmbH Bereitstellung von einem für die Durchführung der IPA und des geeigneten Arbeitsplatzes G: +41 44 910 50 10 Rafisa Informatik GmbH Bernstrasse 88 8953 Dietikon
Legende: G: Geschäftliche Telefonnummer, N: Natel Nummer

Tabelle 1: Projektaufbauorganisation



1.4.Projektmanagement Methode

Als Projektmanagementmethode wurde IPERKA gewählt. IPERKA gibt folgende Projektphasen vor:

Informieren

Die für das Projekt relevanten Informationen werden zusammengetragen, gewertet und geordnet. Die Aufgabenstellung wird dazu genau analysiert und das Wesentliche herauskristallisiert. Unklarheiten sollten in dieser Phase beseitigt werden.

Planen

In dieser Phase wird das weitere Vorgehen geplant. Dazu werden Modelle und Pläne erstellt. Lösungswege werden durchdacht und auf ihre Machbarkeit und Sachdienlichkeit überprüft.

Entscheiden

Gibt es verschiedene Lösungsvarianten wird durch eine Nutzwertanalyse die bestmögliche Variante bestimmt. Die Strategie für das Projekt wird festgelegt.

Realisieren

Das Projekt wird realisiert. Dabei darf das Ziel nicht aus den Augen geraten.

Kontrollieren

Das Produkt wird auf Qualität und Anforderungen geprüft. Wenn nötig und möglich werden Korrekturen vorgenommen.

Auswerten

Das Produkt, der Prozess und die Zusammenarbeit werden reflektiert. Mögliche Optimierungen werden festgehalten.

Der Abschluss einer Projektphase gilt auch als Erreichen eines Meilensteins für dieses Projekt. Die Projektphasen werden im zweiten Teil dieses Berichts dokumentiert.

1.5.Vorkenntnisse

Für eine faire Bewertung werden die Vorkenntnisse des IPA-Kandidaten deklariert.

  • Netbeans IDE: schon viele Projekte damit entwickelt.
  • PHP: relativ viel Erfahrung
  • RIO: seit Projektbeginn, mit Unterbruch, am Mitentwickeln
  • OOP: schon einige Erfahrung, neben PHP auch mit Java
  • SQL: viel in Projekten eingesetzt, vor allem mit MySQL
  • HTML, JavaScript, JQuery: relativ viel Erfahrung
  • CSS: Grundkenntnisse
  • UML Diagramme erstellen: wenig Erfahrung



1.6.Vorarbeiten

Die Vorarbeiten welche für dieses Projekt geleistet wurden, werden deklariert:

  • Zeitplan- und Berichtsvorlage erstellt.
  • Entwicklungsumgebung auf den neusten Stand gebracht

1.7.Firmenstandards

In der Abteilung der Applikationsentwicklung wird hauptsächlich mit der Netbeans IDE entwickelt. Klassen und Methoden sollen für das Verständnis der Entwickler mit erklärenden Kommentaren versehen werden. Die Namensgebung im Programmcode orientiert sich am Java Standard für selbstdefinierte Programmteile.

1.8.Dokumentablage

Für dieses Projekt wird ein separates Repository auf dem Firmeneigenen Source Control Server eingerichtet. Alle für das Projekt relevanten Dateien werden dort hinterlegt. Täglich erfolgt mindestens ein Push auf den Server. Daneben wird täglich ein Backup vom Windows Arbeitsplatz PC erstellt und auf dem Firmenserver hinterlegt. Damit wird das Risiko des Datenverlustes minimiert.

1.9.Zeitplan

Am Zeitplan auf folgender Seite orientiert sich die Projektabwicklung. Zur Nachvollziehbarkeit und für die Auswertung wird die geplante Zeit für die jeweiligen Tätigkeiten der effektiv verwendeten Zeit gegenübergestellt.



Form1

Abbildung 1: Projektzeitplan



1.10.Arbeitsprotokoll

Zu jedem IPA-Tag wird ein Tagesprotokoll geführt. Dies geschieht fortlaufend und wird am Ende des Tages ergänzt. Es wird festgehalten, wie viel Zeit für die verschiedenen Tätigkeiten investiert wurde. Des Weiteren werden benötigte Hilfestellungen sowie Überzeiten protokolliert. Am Ende des Tages wird der Projektfortschritt kommentiert und der Arbeitstag reflektiert.

1.10.1.Tagesprotokoll vom 5.5.2017

Tagesziele
Aufgabenstellung analysieren + Teil 1 vom Bericht schreiben, Zeitplan erstellen, Source Control Repository einrichten, ERM Diagramm erstellen

Zeit Erledigte Arbeiten Erfolge Schwierigkeiten
9:05-9:20 Aufgabenstellung analysieren Für Zeitplan Aufgabe in Arbeitspakete eingeteilt -
9:20-9:55 Zeitplan erstellt - -
10:00-11:30 Teil 1 vom Bericht geschrieben - -
11:32-12:10 Repository einrichten - -
12:54-15:20 ERM Diagramm erstellt und dokumentiert - -
15:30-17:35 Bericht angepasst, Tagesprotokoll ausgefüllt - -

Arbeitszeit Tagestotal Arbeitszeit Total
6h 39min 6h 39min

Projektfortschritt
Das Projekt befindet sich auf gutem Weg. Habe alle Tagesziele erreicht.

Informationsbeschaffung
-

Tagesreflexion
Am Anfang war ich etwas nervös, doch mit der Zeit legte sich das zum Glück. Ich habe das Projekt aufgegleist und bin für den weiteren Verlauf zuversichtlich. Gegen Abend liess die Konzentration etwas nach.

Tabelle 2: Tagesprotokoll vom 5.5.2017



1.10.2.Tagesprotokoll vom 8.5.2017

Tagesziele
1. Expertenbesuch, Use Case Diagramm anfangen

Zeit Erledigte Arbeiten Erfolge Schwierigkeiten
13:30- 14:00 Gespräch vorbereiten, Zeitplan ausgedruckt - -
14:00-15:05 1. Expertenbesuch - -
15:10-15:20 Besprochene Änderungen am Zeitplan vorgenommen - -
15:20- 17:20 Use Case Diagramm angefangen - -
17.20-17:35 Tagesprotokoll nachführen + Push auf Server - -

Arbeitszeit Tagestotal Arbeitszeit Total
4h 10h 39 min

Projektfortschritt
Projekt liegt im Zeitplan. Wenig Fortschritt heute, da auch wenig Zeit zur Verfügung stand.

Informationsbeschaffung
Anwendungsfalldiagramm auf Wikipedia.org nachgeschlagen1

Tagesreflexion
Der Expertenbesuch verlief gut. Herr Amsler hat kleine Änderungen an dem Zeitplan vorgeschlagen, welche ich nach dem Gespräch auch sofort umgesetzt habe. Das Use Case Diagramm wird etwas komplizierter als ich es erwartet hätte. Ich bin zufrieden mit dem Fortschritt, auch wenn dieser heute nicht gross ist.

Tabelle 3: Tagesprotokoll vom 8.5.2017



1.10.3.Tagesprotokoll vom 9.5.2017

Tagesziele
Use Case Diagramm erstellen, Aktivitätsdiagramm erstellen

Zeit Erledigte Arbeiten Erfolge Schwierigkeiten
9:00-12:00 Use Case Diagramm erstellt Schlussendlich bin ich zufrieden mit dem Resultat Ich brauchte mehrere Anläufe bis ich ein zufriedenstellendes Resultat hatte
13:10-
15:30
Aktivitätsdiagramm erstellt - Ich brauchte mehrere Anläufe bis ich ein zufriedenstellendes Resultat hatte
16:00-17:35 Bericht anpassen, Tagesprotokoll schreiben, Push auf Server - -

Arbeitszeit Tagestotal Arbeitszeit Total
6h 55min 17h 34min

Projektfortschritt
Ich werde wohl einige Zeit von der Reservezeit für die Dokumentation einsetzten müssen, da diese bis jetzt etwas zu kurz gekommen ist. Dennoch bin ich zuversichtlich, dass das Projekt erfolgreich abgeschlossen wird.

Informationsbeschaffung
Ich habe mich im Internet über Aktivitätsdiagramme schlau gemacht.2

Tagesreflexion
Die Diagramme zu erstellen war sehr anstrengend. Ich brauchte mehrere Anläufe bis ich zufriedenstellende und vor allem vollständige Resultate hatte. Es hat insgesamt mehr Zeit benötigt als erwartet, denn es war gedacht, dass ich diese Diagramme auch im Bericht beschreibe, doch dazu bin ich noch nicht gross gekommen. Ich werde wohl etwas von der Reserve dafür einsetzen müssen. Doch die Diagramme haben mir geholfen ein Bild vom Projekt zu bekommen.

Tabelle 4: Tagesprotokoll vom 9.5.2017



1.10.4.Tagesprotokoll vom 10.5.2017

Tagesziele
Testkonzept erstellen, Strategie Festlegen

Zeit Erledigte Arbeiten Erfolge Schwierigkeiten
9:30-12:00 Testkonzept angefangen - -
13:00-16:06 Testkonzept erstellt, Testfälle beschrieben - Formatierungsprobleme mit Word
16:20- 17:08 Bericht angepasst, unter anderem Kapitel Use Case erweitert - -
17:08- 17:35 Tagesprotokoll ausgefüllt und push auf Server - -

Arbeitszeit Tagestotal Arbeitszeit Total
6h 51min 24h 25min

Projektfortschritt
Der Meilenstein «Planen» wurde im vorgegebenen Zeitrahmen abgeschlossen.

Informationsbeschaffung
-

Tagesreflexion
Der Tag verlief insgesamt ganz gut und das Projekt machte entsprechende Fortschritte. Als ich für die Testfälle eine neue Ebene in der Überschriften Tabelle erstellen wollte, unterlief mir wohl ein Fehler bei dem es mir diese zerschossen hat. Die Wiederherstellung kostete etwas Zeit und einige Nerven. Bei Der Phase «Entscheiden» bin ich mir unsicher, denn ich habe eigentlich keine verschiedenen Lösungsvarianten die sich herauskristallisiert haben. Das meiste ist schon von der Projektstruktur und der Aufgabenstellung vorgegeben. Ich sehe keine Entscheide die ich im IPERKA Sinne treffen muss. Deshalb gibt es für diese Phase auch wenig zu schreiben. Ich bin froh das die Planungsphase vorbei ist und ich planmässig mit der Implementierung anfangen kann. Nur einzelne Punkt in der Planungsdokumentation bedürfen wohl noch der Anpassung.

Tabelle 5: Tagesprotokoll vom 10.5.2017



1.10.5.Tagesprotokoll vom 11.5.2017

Tagesziele
Entitäten implementieren, Präsentation-Layer implementieren

Zeit Erledigte Arbeiten Erfolge Schwierigkeiten
9:55-10:25 Entitätsklassen erstellt Brauchte dafür viel weniger Zeit als eingeplant
10:25-12:00 Präsentation-Layer angefangen
RIOCalendarWidget gab eine Warnung: «Non Numeric Value encountered». Habe das Problem behoben indem ich die Parameter die per Post kommen mit «FILTER_VALIDATE_INT» behandle.
13:05- 19:10 Präsentation-Layer weiterentwickelt +Aktionen und Businesslogik angefangen

19:10-19:15 Push auf Server


Arbeitszeit Tagestotal Arbeitszeit Total
8h 15min 32h 45min

Projektfortschritt
Bei der Implementierung machte ich grosse Fortschritte. Doch ich fand fast keine Zeit für diesen Bericht.

Informationsbeschaffung
PHP date() im Manual nachgeschaut, um die Formatierungsmöglichkeiten nachzuschlagen.3

Tagesreflexion
Ich freute mich endlich mit der Implementierung des Wochenberichtsmoduls beginnen zu können. Am Anfang ging das auch relativ zügig vorwärts. Doch gegen Abend liess meine Konzentration und damit meine Produktivität ab. Bin zufrieden mit dem Fortschritt, den ich bei der Implementierung erzielte, doch ich habe die Dokumentation vernachlässigt. Ich muss wohl Reservezeit für das Dokumentieren der Phase «Realisieren» einsetzten.

Tabelle 6: Tagesprotokoll vom 11.5.2017



1.10.6.Tagesprotokoll vom 12.5.2017

Tagesziele
Präsentations-Layer implementieren, Aktionen und Businesslogik implementieren

Zeit Erledigte Arbeiten Erfolge Schwierigkeiten
9:55-12:13 Habe das Arbeitsjournal Widget weiterentwickelt - -
12:30-18:08 Weiterentwicklung des RIO Wochenberichtsmodul - -
18:08-18:35 Tagesprotokoll nachführen & push auf Server - -

Arbeitszeit Tagestotal Arbeitszeit Total
7h 23min 40h 8min

Projektfortschritt
Die Implementierung beanspruchte mehr Ressourcen als erwartet. Der Präsentations-Layer benötigt nur noch kleinere Anpassungen, doch die Businesslogik und die Aktionen sind auch noch nicht ganz fertig. Vor allem aber wurde der Bericht vernachlässigt.

Informationsbeschaffung
HTML Textarea und Input Attribute nachgeschlagen.4

Tagesreflexion
Bin wohl etwas in Rückstand geraten, geplant wäre heute, dass das Wochenberichtsmodul fertig implementiert ist. Das heisst nur noch das Logging und die Validierung sollten gemacht werden, doch es fehlen noch einige Funktionen beim Modul. Zum Beispiel funktioniert das Signieren noch nicht richtig. Ich hoffe, ich kann den Rückstand nächste Woche aufholen.

Tabelle 7: Tagesprotokoll vom 12.5.2017



1.10.7.Tagesprotokoll vom 15.5.2017

Tagesziele
Expertenbesuch, Weiterentwicklung des Präsentations-Layer und Business-Logik, Bericht weiterführen;

Zeit Erledigte Arbeiten Erfolge Schwierigkeiten
13:15- 13:45 Zeitplan & Tagesprotokolle nachgeführt, Besuch vorbereitet - -
13:45- 14:35 Besuch von Herrn Amsler (Hauptexperte) Gespräch verlief gut -
14:45-17:19 Weiterentwicklung vom RIO Wochenberichtsmodul: Präsentations-Layer & Business-Logik Konnte etwas vom Rückstand aufholen -
17:19-17.45 Bericht nachgeführt, Tagesprotokoll ausgefüllt, Push auf Server - -

Arbeitszeit Tagestotal Arbeitszeit Total
4h 20 min 44h 28min

Projektfortschritt
Heute konnte ich den Rückstand der Implementierung verringern, aber bei der Doku bin ich immer noch im Rückstand und der Programmcode muss noch kommentiert werden, zum Glück habe ich genügend Reserven eingeplant.

Informationsbeschaffung
-

Tagesreflexion
Der Expertenbesuch verlief aus meiner Perspektive sehr positiv. Heute konnte ich, durch das Wochenende erholt, sehr konzentriert und zielgerichtet arbeiten und konnte die Implementierung des Präsentations-Layer und der Business-Logik & Aktionen fertigstellen. Mit der Dokumentation der Phase «Realisieren» bin ich aber immer noch im Rückstand, doch ich bin zuversichtlich für den weiteren Projektverlauf.

Tabelle 8: Tagesprotokoll vom 15.5.2017



1.10.8.Tagesprotokoll vom 16.5.2017

Tagesziele
Business-Logik und Präsentations-Layer abschliessen, Validierung implementieren, Logging implementieren

Zeit Erledigte Arbeiten Erfolge Schwierigkeiten
9:10-12:00 Programmcode kommentiert - -
12:39-15:17 Validierung implementiert Ging schneller als erwartet. -
15:17-15:57 Logging implementiert Brauchte viel weniger Aufwand als erwartet. Hatte etwas Mühe mit dem Pfad für die Logdatei.
15:57- 16:08 Arbeitsprotokoll nachgeführt - -
16:08-17:30 Meldungen beim Ausloggen und Login angefangen - -
17:30-18:00 Tagesprotokoll ausgefüllt & push auf Server - -

Arbeitszeit Tagestotal Arbeitszeit Total
8h 11min 52h 39min

Projektfortschritt
Die Phase «Realisieren» ist kurz vor dem Abschluss. Nach Zeitplan sollte sie heute abgeschlossen sein. Es fehlen noch die Meldungen beim Einloggen, welche den Benutzer auf offene Arbeitsjournale hinweist und die am Ende der Woche auf das signieren des Wochenberichts hinweisen. Der Bericht ist weiterhin im Rückstand.

Informationsbeschaffung
-

Tagesreflexion
Bin heute recht vorwärtsgekommen, konnte mich auch gut konzentrieren. Konnte weiter den Rückstand verringern. Morgen kommt die Phase «Kontrollieren». Ich hoffe bei den Tests tauchen keine Bugs auf, oder zumindest solche die sich einfach beheben lassen. Ich muss mich auch dringend um den Bericht kümmern. Doch ich bin nach wie vor positiv eingestellt.

Tabelle 9: Tagesprotokoll vom 16.5.2017



1.10.9.Tagesprotokoll vom 17.5.2017

Tagesziele
Realisierungsphase abschliessen, Systemtest durchführen, Qualität der Dokumentation sicherstellen

Zeit Erledigte Arbeiten Erfolge Schwierigkeiten
9:30-11:55 Meldung bei Login implementiert, Singnieren lassen sich nur korrekt ausgefüllte Wochenberichte - -
12:30-17:36 Systemtest durchgeführt - -
17:36-18:15 Tagesprotokoll geführt, Zeitplan nachgeführt & push auf Server


Arbeitszeit Tagestotal Arbeitszeit Total
8h 10min 60h 49min

Projektfortschritt
Das RIO Wochenberichtsmodul ist fertig implementiert und auch getestet worden. Nun muss nur noch der Bericht fertiggestellt werden.

Informationsbeschaffung
-

Tagesreflexion
Der Tag verlief gut und ohne nennenswerten Zwischenfälle. Das Testen war sehr anstrengend und es forderte hohe Konzentration die Tests genau nach dem Testszenario durchzuführen. Nun bin ich erschöpft. Ich freue mich auf den Abschluss dieses Projektes. Heute ist es für mein Wohlbefinden etwas zu warm im Büro geworden. Doch ich war so in die Arbeit vertieft, dass mir erst spät in den Sinn gekommen ist, die Storen herunterzulassen.

Tabelle 10: Tagesprotokoll vom 17.5.2017



1.10.10.Tagesprotokoll vom 18.5.2017

Tagesziele
Qualität der Dokumentation sicherstellen, Reflexion vornehmen, Bericht weiterführen

Zeit Erledigte Arbeiten Erfolge Schwierigkeiten
9:40- 12:00 Bericht korrigiert - -
12:50-18:19 Habe am Bericht weitergeschrieben und kleinere Anpassungen am Code vorgenommen - -
18:19-18:30 Tagesprotokoll geführt, Zeitplan angepasst, push auf Server - -

Arbeitszeit Tagestotal Arbeitszeit Total
8h 68h 49min

Projektfortschritt
Das Projekt befindet sich auf der Zielgeraden.

Informationsbeschaffung
-

Tagesreflexion
Heute war ich etwas unproduktiver als an den anderen Tagen. Ich habe nochmals die Aufgabenstellung und die Bewertungskriterien durchgelesen und habe festgestellt, dass ich den Navigationsbutton bei der Meldung beim Login vergessen habe. Diesen Button und weitere Verbesserungen am Wochenberichtsmodul habe ich noch implementiert. Ich bin erschöpft und möchte das Projekt langsam abschliessen. Morgen kümmere ich mich nur noch um den Bericht, damit dieser sauber ist.

Tabelle 11: Tagesprotokoll vom 18.5.2017



1.10.11.Tagesprotokoll vom 19.5.2017

Tagesziele
Auswertung, Reflexion schreiben, Kurzfassung schreiben, Bericht fertigstellen, Projekt abschliessen, Bericht abgeben.

Zeit Erledigte Arbeiten Erfolge Schwierigkeiten
8:17-12:10 Bericht fertiggestellt, Code hinzugefügt - -
12:35-13:49 Reflexion & Kurzfassung geschrieben - -
13:49-15:27 Formatierungen anpassen, Layout verbessert - -
15:27- 17:03 Tagesprotokoll geführt & push auf Server, finale Version des Berichts auf pkorg.ch hochgeladen - -

Arbeitszeit Tagestotal Arbeitszeit Total
8h 21min 77h 10min

Projektfortschritt
Das Projekt ist abgeschlossen

Informationsbeschaffung
-

Tagesreflexion
Ich bin froh, dass das Projekt abgeschlossen ist und bin mit dem Resultat zufrieden. Heute war ein stressiger Tag.

Tabelle 12: Tagesprotokoll vom 19.5.2017



Teil 2Projekt-Dokumentation

2.1.Kurzfassung

Ausgangssituation

Die Firma Rafisa Informatik wünscht sich für ihre bestehende Webapplikation RIO ein Modul, mit welchem die Lernenden Wochenberichte schreiben können. Für Jeden Arbeitstag sollen sie ihre Arbeit protokollieren und eine kurze Reflexion verfassen.

Das Rafisa interne ERP Programm Rafisa Information & Organisation bietet Module zur Personalverwaltung und Projektmanagement an.

RIO bietet schon viele Funktionen für GUI Elemente und Datenobjekte an. Auch der Persistenz-Layer der die Datenbankentitäten in Objekte umwandelt, oder umgekehrt, ist schon vorhanden. Der Entwickler der dieses Modul umsetzen wird ist schon seit dem Projektbeginn dabei und bringt deshalb viel Erfahrung mit der Architektur von RIO mit.

Umsetzung

Die Analyse der Anforderungen und die Erstellung des Zeitplans waren der erste Schritt um das Projekt starten zu können.

Nachdem das Wochenberichtsmodul mittels Use Case spezifiziert wurde, erfolgte die Implementation.

Die Anforderungen wurden im Code umgesetzt. Dabei wurde viel Wert auf die Wiederverwendbarkeit und Erweiterbarkeit gelegt.

Bestehende Strukturen wurden berücksichtigt und bei Bedarf erweitert.

Ergebnis

Das Wochenberichtsmodul ist erfolgreich implementiert und getestet worden und kann aus Sicht des Entwicklers produktiv eingesetzt werden.

Dieser Bericht, welcher den ganzen Prozess des Projektablaufs beschreibt, ist auch zentraler Bestandteil des Ergebnisses.



2.2.Informieren

Der Projektauftrag wurde genau analysiert und in Arbeitspakete für den Zeitplan eingeteilt. Dieser ist ziemlich klar formuliert. Die Struktur der bestehenden Applikation RIO wurde untersucht, wie auch die Schnittstellen mit dem neu zu erstellenden Modul.

Bei RIO handelt es sich um eine firmenintern verwendete Webseite mit Datenbankanbindung. Serverseitig wird PHP eingesetzt. Die Dokumentbeschreibungssprache HTML sowie CSS sind für die Darstellung, also das GUI, zuständig. Clientseitig werden Aktionen per JavaScript, inklusive JQuery, ausgelöst.

Bei der Datenbank handelt es sich um MySQL.

Für ein besseres Verständnis für die bestehende Applikation wird ein Screenshot gezeigt:

Abbildung 2: Personendetailansicht mit selektiertem Adressen Tab

Oben sieht man den Navigationsbereich der RIO Webseite.

Man sieht links die Details einer Person. Diese können hier auch direkt bearbeitet werden.



Die Projektstruktur von RIO sieht wie folgt aus:

Abbildung 3: Projektstruktur RIO

2.3.Planen

Eine gründliche Planung des zu erstellenden Wochenberichtsmodul für RIO ist für den Erfolg des Projektes von entscheidender Bedeutung. Deshalb wird dieser Phase neben dem Realisieren am meisten Zeit eingeräumt. Die Abläufe, die Architektur und Datenstruktur werden mit verschiedenen Diagrammen modelliert.

2.3.1.Repository einrichten

Auf dem Rafisa SCM Server wurde ein separates Repository für dieses Projekt erstellt. Die vollständige URL lautet: http://4540.hostserv.eu:8080/scm/hg/IPA/p_gabathuler/RIOWochenberichsModul

Der Benutzer p.gabathuler wurde als Besitzer des Repository eingetragen.

Das RIO-Repository Projekt wurde neu lokal geklont. Der Repository Ordner «.hg» wurde gelöscht. Es wurde ein neuer Ordner für die Dokumentation erstellt. Danach fand der erste Commit statt.



2.3.2.Entity Relationship Modell

Um die Abhängigkeit der Datenobjekte zu verstehen und um die Datenbankanpassungen zu planen wurde folgendes ERM Diagramm mit dem MariaDB Workbench Programm erstellt.
Die Zieldatenbank ist eine MariaDB Datenbank namens «rio».

Form2

Primärschlüssel
Fremdschlüssel
Attribut
Entitätstyp
1:n Beziehung











Abbildung 4: Wochenberichtsmodul Entity Relationship Modell

Zum Modell:

Nur die für dieses Projekt relevanten Entitäten wurden abgebildet.

Bei der Namensgebung der Entitäten und Attribute, sowie der Zuweisung der Datentypen, wurde der RIO-Projekt Standard befolgt.

Die schon existierende Tabelle «Person» wurde in gekürzter Fassung dargestellt. Auch bei den neu zu erstellenden Tabellen wurden Attribute weggelassen. Die Datenobjekte erben alle von der Klasse «Entity» und somit übernehmen auch die Unterklassen und damit auch die Datenbankentitäten alle deren Attribute:

Abbildung 5: Entity Attribute

Aus Gründen der Übersichtlichkeit wurden diese Attribute in der Abbildung 4 weggelassen.

2.3.3.Use Case Wochenberichtsmodul RIO

Die Anwendungsfälle vom zu erstellenden Modul wurden untersucht. Sie stellen das zu erwartende Verhalten des RIO Wochenberichtsmodul dar.

Use Case Wochenbericht führen

Aktoren: Lernender
Vorgesetzter

Haupt Erfolgsszenario:

  1. Lernender meldet sich bei RIO an (Login).
  2. Lernender navigiert zur Wochenberichtsansicht.
  3. Lernender navigiert zur Detail Ansicht des Arbeitsjournals des aktuellen Tages.
  4. Lernender füllt Arbeitsjournal aus.
  5. Lernender erstellt Task.
  6. Lernender füllt Task aus.
  7. Lernender wiederholt Schritte 5 und 6 so oft wie nötig (max. 7 Mal)
  8. Lernender speichert Arbeitsjournal
  9. Falls es der letzte Arbeitstag des Lernenden in der Firma ist, signiert zuerst er seinen Wochenbericht, danach sein Vorgesetzter.
  10. Schritte 1-9 werden für jeden Arbeitstag wiederholt (Montag – Freitag)

Erweiterungen:

1a. Lernender meldet sich das erste Mal in dieser Woche an.

1a1. Wochenbericht wird initialisiert.

1b. Lernender meldet sich das erste Mal an diesem Tag an.

1b1. Arbeitsjournal für den entsprechenden Tag wird initialisiert.

1c. Es sind leere Arbeitsjournale beim Lernenden vorhanden.

1c1. Der Lernende wird vom Programm dazu aufgefordert seine Arbeitsjournale nachzutragen.

1d. Es ist Freitag und der aktuelle Wochenbericht wurde noch nicht signiert.

1d1. Lernender wird mit einer Meldung auf das Fehlen der Signatur hingewiesen.

Bemerkungen:

Das Szenario scheitert immer, wenn der Lernende nicht mitmacht. Ist die Kooperation des Lernenden ungenügend ist eine Intervention vom Vorgesetzten notwendig. Das ist aber nicht Bestandteil vom System und deshalb wird darauf nicht näher eingegangen.

Falls der Lernende die ganze Woche abwesend sein sollte wird vom System kein Wochenbericht erstellt, es gibt also auch nichts zu signieren oder auszufüllen.

Sollte der Lernende einen einzelnen Tag abwesend sein, so wird für diesen Tag kein Arbeitsjournal erstellt. Der Wochenbericht wird trotzdem vom System als vollständig erachtet und bei der Validierung als korrekt befunden.

Geplant ist, dass für Fehltage der Grund für die Absenz angezeigt wird. Diese Gründe können zum Beispiel sein: Schule, Überbetrieblicher Kurs oder Abwesenheit aus gesundheitlichen Gründen. Diese Gründe werden in Zukunft im Präsenzzeitenmodul erfasst. Da dieses aber noch nicht existiert, wird in der Wochenberichtsansicht für den jeweiligen Tag kein Grund angezeigt.

Es folgt das dazugehörige Use Case Diagramm:



Abbildung 6: Use Case Wochenberichtsmodul RIO Diagramm



2.3.4.RIO Wochenberichtsmodul Aktivitätsdiagramm

In folgendem Diagramm werden die Daten- und Kontrollflüsse vom Wochenberichtsmodul RIO dargestellt. Die untersuchte Aktivität ist das tägliche Nachführen des Wochenberichts durch die Lernenden.

Abbildung 7: Wochenberichtsmodul RIO Aktivitätsdiagramm

2.3.5. Testkonzept

Das RIO Wochenberichtsmodul wird einem System Test, genauer gesagt einem Black Box Funktionstest, unterzogen. Bei diesem Test werden alle spezifizierten Anforderungen an das RIO Wochenberichtsmodul überprüft.

Mit folgendem Testsystem wird der Test stattfinden:

  • Betriebssystem: Windows 10
  • PHP Version 7.1.1
  • Datenbank: MariaDB Version 10.1.21
  • Browser: Chrome Version 58.0

Das Testsystem dient sowohl als Server, als auch Client. Es wird also lokal auf dem Host getestet.

Der Test wird von Philipp Gabathuler durchgeführt werden.

Folgende Testfälle werden bei der Projektphase «Kontrollieren» geprüft. Für diese Testfälle gelten allgemein folgende Vorbedingungen:

RIO und die Datenbank sind korrekt installiert. Sowohl der Browser, als auch der Webserver und Datenbankserver sind gestartet. Es existieren je zwei Accounts die als Lernender und Vorgesetzter dienen. Der aktive Browser Tab ist auf die RIO Webseite navigiert worden.



2.3.5.1.Testfall Login ohne Meldungen

Beschreibung: Ein registrierter Benutzer sollte sich erfolgreich bei RIO anmelden können. Bei Bedarf zeigt RIO dem Benutzer bestimmte Meldungen nach erfolgreichem Login an.

Vorbedingungen: Es sind keine unsignierten Wochenberichte vorhanden, ausser dem der aktuellen Woche, ausser das Serverdatum ist Freitag.

Testschritte:

  1. Benutzername und Passwort des registrierten Benutzers eingeben
  2. Auf Login-Button klicken

Erwartetes Resultat: Normale RIO Ansicht nach Login wird angezeigt ohne wochenberichtsspezifische Meldungen.

2.3.5.2.Testfall Login mit Erinnerung an das Signieren & Ausfüllen

Beschreibung: Ein registrierter Benutzer sollte sich erfolgreich bei RIO anmelden können. Bei unsignierten Wochenberichten, wird der Benutzer mit einer Meldung nach dem Login an das Signieren der unsignierten Wochenberichte und das Ausfüllen der Arbeitsjournale erinnert.

Vorbedingungen: Es existieren unsignierte Wochenberichte von letzter und vorletzter Woche, neben dem der aktuellen Woche

Testschritte:

  1. Benutzername und Passwort des registrierten Benutzers eingeben
  2. Auf Login-Button klicken

Erwartetes Resultat: Normale RIO Ansicht nach Login wird angezeigt, inklusive Erinnerung an das Signieren der unsignierten Wochenberichte und das Ausfüllen der Arbeitsjournale.

2.3.5.3.Testfall Arbeitsjournal editieren

Beschreibung: Der Benutzer füllt das Arbeitsjournal aus und speichert die Daten.

Vorbedingungen: Der Benutzer hat sich erfolgreich bei RIO eingeloggt. Das Arbeitsjournal des aktuellen Tages ist noch leer.

Testschritte:

  1. Benutzer navigieren zu seiner Wochenberichtsansicht.
  2. Benutzer navigiert zur Detailansicht des zu editierenden Arbeitsjournales
  3. Korrektes Ausfüllen des Arbeitsjournal Formulars
  4. Arbeitsjournal speichern
  5. Für das Arbeitsjournal zwei Tasks erstellen und deren Formulare korrekt ausfüllen.
  6. Seite neu laden
  7. Zur Detailansicht des editierten Arbeitsjournales navigieren

Erwartetes Resultat: Die eingegebenen Daten werden angezeigt, sind also persistiert worden.



2.3.5.4.Testfall Abmelden ohne Meldung

Beschreibung: Der Benutzer meldet sich von RIO ab

Vorbedingungen: Der Benutzer hat sich erfolgreich bei RIO eingeloggt. Das Arbeitsjournal des aktuellen Tages ist korrekt ausgefüllt.

Testschritte:

  1. Abmelden Button betätigen

Erwartetes Resultat: Der Benutzer wird ausgeloggt und die Login Ansicht wird angezeigt.

2.3.5.5.Testfall Abmelden mit Meldung

Beschreibung: Der Benutzer meldet sich von RIO ab

Vorbedingungen: Der Benutzer hat sich erfolgreich bei RIO eingeloggt. Das Arbeitsjournal des aktuellen Tages ist nicht korrekt ausgefüllt.

Testschritte:

  1. Abmelden Button betätigen

Erwartetes Resultat: Der Benutzer wird nicht ausgeloggt stattdessen wird ein Dialog mit der Aufforderung, das Arbeitsjournal auszufüllen angezeigt.

2.3.5.6.Testfall Wochenbericht signieren

Beschreibung: Der Besitzer des Wochenberichts und sein Fachvorgesetzter können den Wochenbericht, wenn alle Arbeitsjournale korrekt ausgefüllt wurden, signieren.

Vorbedingungen: Alle Arbeitsjournale des Wochenberichts sind korrekt ausgefüllt. Der Benutzer hat sich erfolgreich bei RIO eingeloggt. Der Wochenbericht wurde noch nicht signiert und die Arbeitsjournale lassen sich vom Besitzer noch bearbeiten.

Testschritte:

  1. Login als Besitzer
  2. zu seiner Wochenberichtsansicht navigieren
  3. Signieren-Button betätigen
  4. Abmelden Button betätigen
  5. Login als Fachvorgesetzter
  6. zur Wochenberichtsansicht des Besitzer-Benutzers navigieren
  7. und denselben Wochenbericht auswählen
  8. Signieren-Button betätigen
  9. Abmelden Button betätigen
  10. Login als Besitzer
  11. zu seiner Wochenberichtsansicht navigieren
  12. ein Arbeitsjournal des signierten Wochenberichts öffnen

Erwartetes Resultat: Die Arbeitsjournale und deren Tasks vom signierten Wochenbericht lassen sich nicht mehr editieren.



2.3.5.7.Testfall Wochenbericht mit nicht ausgefüllten Arbeitsjournalen

Beschreibung: Der Besitzer des Wochenberichts und sein Fachvorgesetzter können den Wochenbericht, wenn alle Arbeitsjournale korrekt ausgefüllt wurden, signieren.

Vorbedingungen: Es existieren noch nicht korrekt ausgefüllte Arbeitsjournale beim Wochenbericht. Der Benutzer hat sich erfolgreich bei RIO eingeloggt. Der Wochenbericht wurde noch nicht signiert.

Testschritte:

  1. Login als Besitzer
  2. zu seiner Wochenberichtsansicht navigieren
  3. Signieren-Button betätigen

Erwartetes Resultat: Der Wochenbericht wird nicht signiert, stattdessen wird eine Meldung angezeigt, in welcher aufgelistet wird, welche Arbeitsjournale noch nicht ausgefüllt sind und welche Daten genau fehlen.

2.3.5.8.Testfall Arbeitsjournal eines anderen Benutzers einsehen

Beschreibung: Die Arbeitsjournale können auch von anderen Usern eingesehen aber nicht bearbeitet werden.

Vorbedingungen: -

Testschritte:

  1. Login
  2. zu Wochenberichtsansicht eines anderen Benutzers navigieren
  3. ein Arbeitsjournal öffnen

Erwartetes Resultat: Die Daten werden korrekt angezeigt, lassen sich jedoch nicht editieren.

Um die Korrektheit des Validierens der Arbeitsjournale und Tasks zu gewährleisten, werden separate Tests durchgeführt, die genau überprüfen, ob die in der Aufgabenstellung spezifizierten Validierungen korrekt funktionieren. Für diese Tests werden hier aus zeitlichen Gründen keine eigenen Testfälle definiert.

2.4.Entscheiden

Das zu erstellende Wochenberichtsmodul RIO ist nun genau spezifiziert. Probleme sind soweit keine aufgetreten und es zeichnen sich auch keine ab. Eigentliche Entscheide müssen nicht mehr getroffen werden. Das liegt daran, dass bei diesem Projekt die Rahmenbedingen sehr genau vorgegeben sind. Nun kann mit dem Realisieren des Projektes, das heisst der Implementation des RIO Wochenberichtsmodul angefangen werden.



2.5.Realisieren

Das RIO Wochenberichtsmodul wird implementiert. Es wird hier nicht auf Details vom Programmcode eingegangen, sondern die Struktur beschrieben und auf Abweichungen zur Planung hingewiesen.

2.5.1.Entitäten Implementieren

Die Datenobjekte von RIO haben eine vorgegebene Struktur. Sie erben von der Basisklasse Entity. Sie wurden genau nach dem Entity Relationship Modell erstellt. Die einzige Anpassung war, dass die Attribute «Arbeitszeit» beim Arbeitsjournal in zwei Attribute unterteilt wurde, nämlich «TotalStunden» und «TotalMinuten» und entsprechend auch beim Task das Attribut «Zeit» in «Stunden» und «Minuten». Dies vereinfacht die Handhabung der Daten beim Speichern und Anzeigen, da keine Umrechnungen von Stunden in Minuten und umgekehrt stattfinden muss.

2.5.2.Präsentations-Layer Implementieren

Die Wochenberichtsansicht beinhaltet den Signieren Button und den Wochenberichtskalender. Für Buttons und Kalender bestehen schon Widgets. Am RIOCalendarWidget waren kleinere Anpassungen nötig, damit man sieht für welche Tage Arbeitsjournale vorhanden sind und man die Arbeitsjournale einsehen kann. Das Resultat sieht folgendermassen aus:

Für die Abeitsjournal-Ansicht wurde ein eigenes Widget erstellt. Dieses setzt sich wiederum aus schon bestehenden Widgets, wie RIOTextarea und RIOInupt, zusammen. Für die Tasktabelle wurde das RIOTable Widget verwendet, auch hier waren einige Anpassungen nötig. Es wurden RIOTextAreaRenderer und RIOInputTimeRenderer erstellt um die einzelnen Task bei Bedarf editierbar machen zu können. Auch RIOTextarea und RIOInupt mussten für die Anforderungen an das Wochenberichtsmodul angepasst werden.

2.5.3.Business Logik und Aktionen implementieren

Die Aktionen, welche die Benutzer auslösen können, wurden nach dem gleichen Schema wie bestehende RIO Aktionen implementiert. Die Aktionen bestehen jeweils aus einem clientseitigen JavaScript Teil, welcher die benötigten Daten per Ajax an den «ActionHypervisor» übermittelt. Dieser löst den entsprechenden, serverseitigen Teil der Aktion aus. Der PHP Teil schickt bei Bedarf Daten zurück.

Es wurde eine Hilfsklasse namens «WeeklyReportControl» erstellt. Diese bietet diverse Funktionen an, welche die anderen Applikationsteile bei der Handhabung der Wochenberichtsdaten unterstützen.

2.5.4.Validierung implementieren

Es wurden Validator-Klassen implementiert, welche die geforderten Validierungen bei den Wochenberichtsdaten vornehmen.

2.5.5.Logging implementieren

Da alle Aktionen über den ActionHypervisor laufen, wurde dort das geforderte Logging implementiert. Für jeden Tag wird ein separates Logfile in den neu erstellten Ordner «log» geschrieben. Dort wird die Zeit, Aktionsname und die ID des Benutzers festgehalten.

2.6.Kontrollieren

Die Tests wurden, genau wie im Testkonzept beschrieben, durchgeführt. Es folgt die Auswertung der Tests.

2.6.1.Testprotokoll

Testfall Erfolg Bemerkungen
Login ohne Meldungen -
Testfall Login mit Erinnerung an das Signieren & Ausfüllen -
Arbeitsjournal editieren -
Abmelden ohne Meldung -
Abmelden mit Meldung -
Testfall Wochenbericht signieren -
Testfall Wochenbericht mit nicht ausgefüllten Arbeitsjournalen -
Testfall Arbeitsjournal eines anderen Benutzers einsehen
-

Tabelle 13: Auswertung des Systemtests

Alle beschriebenen Testfälle wurden erfolgreich getestet. Dabei wurden kein Fehlverhalten vom RIO Wochenberichtsmodul festgestellt.



2.6.2.Überprüfung der Arbeitsjournal-Validierung

Ob die Arbeitsjournal- und Task-Validierung nach den Anforderungen funktionieren, wird hier überprüft. Für jedes Feld werden Positivtests und Negativtests durchgeführt. Bei den Positivtests wird erwartet, dass die Daten persistiert werden, d.h. alle Felder müssen Ihre Bedingungen erfüllen, bei Negativtests wird erwartet, dass eine entsprechende Meldung dem Benutzer angezeigt wird. Die Meldung zeigt dem Benutzer an, welche Felder er nicht korrekt ausgefüllt hat.

Feld Bedingung Erfolg Bemerkung
Tagesziel zwischen 40-560 Zeichen -
Total Stunden Zahlen 0-12 Es lassen sich auch Zeichen wie «/*-+.» einfügen aber die Daten werden nicht so gespeichert. 8.1 zum Beispiel wird als 8 gespeichert.
Total Minuten Zahlen 0-59 Es lassen sich auch Zeichen wie «/*-+.» einfügen aber die Daten werden nicht so gespeichert. 8.1 zum Beispiel wird als 8 gespeichert.
Bemerkungen maximal 560 Zeichen -
Nächste Arbeitsschritte zwischen 40-560 Zeichen -
Task Stunden Zahlen 0-12 Es lassen sich auch Zeichen wie «/*-+.» einfügen aber die Daten werden nicht so gespeichert. 8.1 zum Beispiel wird als 8 gespeichert.
Task Minuten Zahlen 0-59 Es lassen sich auch Zeichen wie «/*-+.» einfügen aber die Daten werden nicht so gespeichert. 8.1 zum Beispiel wird als 8 gespeichert.
Erledigte Aufgaben Zwischen 15-120 -
Erfolge Max. 240 Zeichen -
Schwierigkeiten Max. 240 Zeichen -

Tabelle 14: Auswertung der Test zur Überprüfung der Validierung

Es wurde festgestellt, dass die Zahlenwerte als String an den Server übermittelt werden. Dort findet ein implizites Typecasting zu «int» statt. Ob der Wert im erwarteten Bereich liegt, wird danach korrekt überprüft. Man könnte das so lösen, dass man den Wert vor dem Typecasting mit dem Wert danach, zu «string» zurückgewandelt, vergleicht und nur, wenn beide gleich sind, den Wert persistiert und sonst eine Meldung dem User anzeigt. Clientseitig könnte man die Eingabe dieser Zeichen mittel einem JavaScript Key-Listener bei den Inputs verhindern.

Ansonsten sind bei den Tests keine Unregelmässigkeiten festgestellt worden.



2.7.Auswerten

Bei diesem Projekt musste ich meine Fähigkeiten als Applikationsentwickler unter Beweis stellen und zeigen was ich mir während meiner Lehrzeit an Wissen und Fähigkeiten angeeignet habe. Vor der IPA und am Anfang derer Stand ich sehr unter Druck. Doch während der Projektarbeit gewann ich nach und nach mein Selbstvertrauen zurück und kann nun mit Stolz die Arbeit so präsentieren wie sie vorliegt.

Diese Arbeit im speziellen hat mir gezeigt wie wichtig vorausschauendes Planen und strukturiertes Vorgehen bei einer Projektarbeit sind. Bei der Zeitplanung habe ich mich etwas verschätzt. Ich habe den Aufwand für die Implementation etwas unterschätzt. Während des Programmierens konnte ich nicht parallel die Dokumentation weiterführen. Eins Nach dem anderen funktioniert bei mir besser.

Die Anforderungen, welche an meine Arbeit gestellt wurden, konnte ich, zumindest aus meiner Sicht, mehr als zufriedenstellend umsetzen.

Klar hätte man im Bericht das Projekt noch genauer Beschreiben und den Code noch optimieren können. Doch wenn man den begrenzten Zeitrahmen und die harte Deadline mitberücksichtigt, denke ich, kann man mit dem Resultat zufrieden sein.

2.8. Glossar

ActionHypervisor Zentrales Objekt welche, im RIO, alle Aktionen
überprüft und ausführt.

Arbeitsjournal DasArbeitsjournalist das Arbeitstagebuch, die begleitend zu einem Arbeitsprozess erstellt wird.

RIO Rafisa Information & Organisation
Bezeichnung für unser Enterprise-Resource- Planning System.
Es handelt sich um eine intern verwendete Webseite zur Personalverwaltung. Das Wochenberichtsmodul wird für diese Webseite entwickelt. Eine Lagerverwaltungssoftware zur Materialbedarfsplanung befindet sich momentan noch im Aufbau.

Entity Abstrakte Klasse, welche als Bauplan für die Datenobjekte dient.

RIOWidget Abstrakte Klasse für GUI Bestandteile.

Validator DieValidator-Klassen sind Objekte, das dieKorrektheit der im Objekt gespeicherten Werte überprüfen.

Wochenbericht Bericht, in dem die die Ereignisse einer Woche festgehalten sind.

2.9.Quellen- & Literaturverzeichnis

Teil 3Anhang

3.1.Abbildungsverzeichnis

Abbildung 1: Projektzeitplan 10

Abbildung 2: Personendetailansicht mit selektiertem Adressen Tab 23

Abbildung 3: Projektstruktur RIO 24

Abbildung 4: Wochenberichtsmodul Entity Relationship Modell 25

Abbildung 5: Entity Attribute 25

Abbildung 6: Use Case Wochenberichtsmodul RIO Diagramm 27

Abbildung 7: Wochenberichtsmodul RIO Aktivitätsdiagramm 28



3.2.Tabellenverzeichnis



Tabelle 1: Projektaufbauorganisation 7

Tabelle 2: Tagesprotokoll vom 5.5.2017 11

Tabelle 3: Tagesprotokoll vom 8.5.2017 12

Tabelle 4: Tagesprotokoll vom 9.5.2017 13

Tabelle 5: Tagesprotokoll vom 10.5.2017 14

Tabelle 6: Tagesprotokoll vom 11.5.2017 15

Tabelle 7: Tagesprotokoll vom 12.5.2017 16

Tabelle 8: Tagesprotokoll vom 15.5.2017 17

Tabelle 9: Tagesprotokoll vom 16.5.2017 18

Tabelle 10: Tagesprotokoll vom 17.5.2017 19

Tabelle 11: Tagesprotokoll vom 18.5.2017 20

Tabelle 12: Tagesprotokoll vom 19.5.2017 21

Tabelle 13: Auswertung des Systemtests 33

Tabelle 14: Auswertung der Test zur Überprüfung der Validierung 34





3.3.Listing des Programmcodes

3.3.1.Eigenentwicklung

Diese Dateien Wurden vom Kandidaten für das RIO Wochenberichtsmodul während der IPA geschrieben.

3.3.1.1.Arbeitsjournal.php

<?php


/**

* Arbeitsjournal: Bauplan für Arbeitsjourmnaldatenobjekte.

*

* Instanzierungsbeispiel: <code>new Arbeitsjournal()</code>

*

* @author p.gabathuler

*/

class Arbeitsjournal extends Entity {


const WOCHENBERICHT = „Wochenbericht“;

const DATUM = „Datum“;

const TAGESZIEL = „Tagesziel“;

const TOTAL_STUNDEN = „TotalStunden“;

const TOTAL_MINUTEN = „TotalMinuten“;

const BEMERKUNGEN = „Bemerkungen“;

const WEITERE_ARBEITSSCHRITTE = „WeitereArbeitsschritte“;

private static $fields = null;

/**

* Die Attribute der Entität werden hier deklariert, inklusive Metadaten.

*

* @return array : array mit den Attributdefinitionen

*/

public static function fields() {

if (self::$fields === null) {

$parentFields = parent::fields();

$fields = [

self::WOCHENBERICHT ⇒ [RIOEC::TYPE ⇒ RIOEC::ENTITY, Wochenbericht::class],

self::DATUM ⇒ [RIOEC::TYPE ⇒ RIOEC::INTEGER],

self::TAGESZIEL ⇒ [RIOEC::TYPE ⇒ RIOEC::TEXT],

self::TOTAL_STUNDEN ⇒ [RIOEC::TYPE ⇒ RIOEC::INTEGER],

self::TOTAL_MINUTEN ⇒ [RIOEC::TYPE ⇒ RIOEC::INTEGER],

self::BEMERKUNGEN ⇒ [RIOEC::TYPE ⇒ RIOEC::TEXT],

self::WEITERE_ARBEITSSCHRITTE ⇒ [RIOEC::TYPE ⇒ RIOEC::TEXT]

];

self::$fields = $parentFields + $fields;

}

return self::$fields;

}

}




3.3.1.2.Wochenbericht.php

<?php


/**

* Wochenbericht ist der Bauplan für die Wochenberichte. Pro Jahr, Wochennummer

* und Besitzer existiert genau ein Wochenbericht. Die Datenbank Entität Wochenbericht

* wird auf ein Objekt dieser Klasse gemappt, umgekehrt funktioniert das auch.

*

* Instanzierungsbeispiel: <code>new Wochenbericht()</code>

*

* @author p.gabathuler

*/

class Wochenbericht extends Entity {


const JAHR = „Jahr“;

const MONAT = „Monat“;

/*

* Sagt aus für welche Woche des Jahres der Wochenbericht gilt.

*/

const WOCHENNUMMER = „Wochennummer“;

const BESITZER = „Person“;

const SIGNATURE_BESITZER = „SignaturBenutzer“;

const SIGNATURE_VORGESETZTER = „SignaturVorgesetzter“;

private static $fields = null;

/**

* Die Attribute der Entität werden hier deklariert, inklusive Metadaten.

*

* @return array : array mit den Attributdefinitionen

*/

public static function fields() {

if (self::$fields === null) {

$parentFields = parent::fields();

$fields = [

self::JAHR ⇒ [RIOEC::TYPE ⇒ RIOEC::INTEGER],

self::WOCHENNUMMER ⇒ [RIOEC::TYPE ⇒ RIOEC::INTEGER],

self::MONAT ⇒ [RIOEC::TYPE ⇒ RIOEC::INTEGER],

self::BESITZER ⇒ [RIOEC::TYPE ⇒ RIOEC::ENTITY, RIOEC::CLASS_NAME ⇒ Person::class],

self::SIGNATURE_BESITZER ⇒ [RIOEC::TYPE ⇒ RIOEC::ENTITY, RIOEC::CLASS_NAME ⇒ Person::class],

self::SIGNATURE_VORGESETZTER ⇒ [RIOEC::TYPE ⇒ RIOEC::ENTITY, RIOEC::CLASS_NAME ⇒ Person::class]

];

self::$fields = $parentFields + $fields;

}

return self::$fields;

}

}




3.3.1.3.Task.php

<?php


/**

* Description of Task

*

* @author p.gabathuler

*/

class Task extends Entity {


const STUNDEN = „Stunden“;

const MINUTEN = „Minuten“;

const ERLEDIGTE_AUFGABEN = „ErledigteAufgaben“;

const ERFOLGE = „Erfolge“;

const SCHWIERIGKEITEN = „Schwierigkeiten“;

const ARBEITSJOURNAL = „Arbeitsjournal“;

private static $fields = null;

public static function fields() {

if (self::$fields === null) {

$parentFields = parent::fields();

$fields = [

self::STUNDEN ⇒ [RIOEC::TYPE ⇒ RIOEC::INTEGER],

self::MINUTEN ⇒ [RIOEC::TYPE ⇒ RIOEC::INTEGER],

self::ERLEDIGTE_AUFGABEN ⇒ [RIOEC::TYPE ⇒ RIOEC::STRING],

self::ERFOLGE ⇒ [RIOEC::TYPE ⇒ RIOEC::STRING],

self::SCHWIERIGKEITEN ⇒ [RIOEC::TYPE ⇒ RIOEC::STRING],

self::ARBEITSJOURNAL ⇒ [RIOEC::TYPE ⇒ RIOEC::ENTITY, RIOEC::CLASS_NAME ⇒ Arbeitsjournal::class]

];

self::$fields = $parentFields + $fields;

}

return self::$fields;

}

}


3.3.1.4.RIOContainerWeeklyReportWidget.php

<?php


/**

* Hier wird die Wochenberichtsansicht definiert. Sie besteht aus dem RIOCalendarWidget,

* welches einen Kalendar darstellt. Beim Kalender kann man zwischen Jahres, Monats, und

* Wochenansicht navigieren. Über dem Kalender wird ein Button zum Signieren des Wochenberichts angezeigt.

*

* Durch <code>$weeklyReportCalendar→enableWeeklyReportControl(true);</code>

* wird der Kalender so eingestellt, dass in der Monats und Wochenansicht

* angezeigt wird, wenn für einen Tag ein Arbeitsjournal existiert. Ausserdem wird in Der Wochenansicht

* wenn ein Journal existiert, die Aktion um die Arbeisjournal Ansicht aufzurufen aktiviert.

*

* Instanziert wird dieses Widget mit <code>new RIOContainerWeeklyReportWidget($owner)</code>

* Wobei <code>$owner</code> der Besitzer der Wochenberichte ist.

*

* @author p.gabathuler

*/

class RIOContainerWeeklyReportWidget extends RIOWidget {


private $person;

private $calendarWidget;


/*

* Konstrukor Methode

*

* @param Person : Der Besitzer der Wochenberichte

*

* @return RIOContainerWeeklyReportWidget

*/

public function __construct(Person $person) {

$this→person = $person;

$this→guid = self::class . '-' . $this→person;

}


/*

* Stellt das RIOContainerWeeklyReportWidget zusammen.

* Das heisst der HTML Code wird vorbereitet und in der Membervariable

* htmlcode gespeichert.

*

* Speichert in der Session die ID des Besitzers

* der Wochenberichte, welche angezeigt werden

*

*/

protected function initializeComponent() {

$_SESSION[Wochenbericht::class .„Owner“] = $this→person→get(Person::ID);

$weeklyReportCalendar = new RIOCalendarWidget();

$weeklyReportCalendar→enableWeeklyReportControl(true);

$this→calendarWidget = $weeklyReportCalendar;


$code = '<div data-person=„' . $this→person . '“ class=„' . self::class . '“ id=„' . $this→guid . '“>';

$code .= '<table class=\'' . self::class . 'Table\'>';

$code .= '<tr>';

$code .= '<td>';

$code .= $weeklyReportCalendar;

$code .= '</td>';

$code .= '</tr>';

$code .= '</table>';

$code .= '</div>';

$this→htmlCode = $code;

}


}


3.3.1.5.RIOWorkJournalWidget.php

<?php



/**

* RIOWorkJournalWidget stellt die Die Ansicht zum Einsehen und/oder bearbeiten

* von einem bestimmten Arbeitsjournal zusammen. Nur wenn der entsprechende

* Wochenbericht noch nicht signiert wurde und der Besitzer des Berichts angemeldet ist,

* lässt sich das Arbeitsjournal bearbeiten. Falls dies nicht der Fall ist,

* werden die Textareas ausgeschaltet und die Buttons zum bearbeiten nicht

* angezeigt.

*

* Ein Button zum Persistieren der eingegebenen Daten wird bereitgestellt.

*

* Stellt Textareas zur Texteingabe und Input Felder für die Zeitangaben zur

* verfügung.

*

* Stellt eine Tabelle mit den zum Journal zugehörigen Tasks zusammen

* mit Textareas zur Texteingabe und input Felder für die Zeitangaben.

*

* Stellt Buttons zum neue Task zu erstellen und zum Tasks löschen bereit.

*

* Instanzierungsbeispiel: <code>new RIOWorkJournalWidget($journalID)</code>

*

* @author p.gabathuler

*/

class RIOWorkJournalWidget extends RIOWidget {

/**

* ID des anzuzeigende Arbeitsjournal

*

* @var String

*/

private $journalID;


/*

* Konstrukor Methode

*

* @param $journalID : ID des anzuzeigende Arbeitsjournal

*

* @return RIOWorkJournalWidget

*/

public function __construct(String $journalID) {

$this→journalID = $journalID;

}

/**

* Das RIOWorkjournalWidget wird zusammengestellt und für die Anzeige vorbereitet.

* Der HTML Code wird in der Membervariable <code>htmlcode</code> gespeichert.

*/

protected function initializeComponent() {

$journal = RIOPDOPersister::getInstance()→load($this→journalID);

$report = $journal→get(Arbeitsjournal::WOCHENBERICHT);

$reportIsSigned = WeeklyReportControl::isReportSigned($report);

$userEqualsOwner = ($report→get(Wochenbericht::BESITZER)→get(Person::ID) === RIOPresenterHelper::getUserId());

$isEditable = (!$reportIsSigned && $userEqualsOwner);

if ($reportIsSigned) {

$signedInfo = new RIOCustomWidget(„<p>Der Wochenbericht ist signiert.</p>“);

$this→add($signedInfo);

}

if ($isEditable) {

$saveButton = new RIOButton(new RIOSaveJournalAction($this→journalID));

$this→add($saveButton);

}

$goalLabel = new RIOLabel($journal, Arbeitsjournal::TAGESZIEL);

$this→add($goalLabel);

$this→add(new RIOCustomWidget('<hr>'));

$goalTextArea = new RIOTextarea($journal, Arbeitsjournal::TAGESZIEL);

$goalTextArea→setPlaceholderText(„Schreibe hier deine Ziele für diesen Tag auf.“);

$goalTextArea→setColAttributeTo(80);

$goalTextArea→setRowAttributeTo(7);

$goalTextArea→setMaxTextLength(560);

$this→add($goalTextArea);

$workTimeLabel = new RIOCustomWidget(„<label>Arbeitszeit Total</label>“);

$workTimeHourField = new RIOInput($journal, Arbeitsjournal::TOTAL_STUNDEN);

$workTimeHourField→setType('number');

$workTimeHourField→setMaxLength(2);

$workTimeHourField→setMinimum(0);

$workTimeHourField→setMaximum(12);

$untitHour = new RIOCustomWidget(„<section>h</section>“);

$workTimeMinutesField = new RIOInput($journal, Arbeitsjournal::TOTAL_MINUTEN);

$workTimeMinutesField→setType('number');

$workTimeMinutesField→setMaxLength(2);

$workTimeMinutesField→setMinimum(0);

$workTimeMinutesField→setMaximum(59);

$untitMinutes = new RIOCustomWidget(„<section>min</section>“);

$this→add($workTimeLabel);

$this→add(new RIOCustomWidget('<hr>'));

$this→add($workTimeHourField);

$this→add($untitHour);

$this→add($workTimeMinutesField);

$this→add($untitMinutes);

$this→add(new RIOCustomWidget(„<label>Tasks</label>“));

$this→add(new RIOCustomWidget('<hr>'));

if ($isEditable) {

$newTaskButton = new RIOButton(new RIONewTaskAction($this→journalID));

$this→add($newTaskButton);

$deleteTaskButton = new RIOButton(new RIODeleteTaskAction());

$this→add($deleteTaskButton);

}

$taskTable = self::createTasksTable($journal);

$this→add($taskTable);

$commentLabel = new RIOLabel($journal, Arbeitsjournal::BEMERKUNGEN);

$this→add($commentLabel);

$this→add(new RIOCustomWidget('<hr>'));

$commentTextArea = new RIOTextarea($journal, Arbeitsjournal::BEMERKUNGEN);

$commentTextArea→setPlaceholderText(„Schreibe hier auf wie du deinen Tag erlebet hast.“);

$commentTextArea→setColAttributeTo(80);

$commentTextArea→setRowAttributeTo(7);

$commentTextArea→setMaxTextLength(560);

$this→add($commentTextArea);

$displayName = „Nächste Arbeitsschritte“;

$nextStepsLabel = new RIOLabel($journal, $displayName);

$this→add($nextStepsLabel);

$this→add(new RIOCustomWidget('<hr>'));

$nextStepsTextArea = new RIOTextarea($journal, Arbeitsjournal::WEITERE_ARBEITSSCHRITTE);

$nextStepsTextArea→setPlaceholderText(„Schreibe hier deine nächsten Arbeittsschritte auf.“);

$nextStepsTextArea→setColAttributeTo(80);

$nextStepsTextArea→setRowAttributeTo(7);

$nextStepsTextArea→setMaxTextLength(560);

$this→add($nextStepsTextArea);

if (!$isEditable) {

$goalTextArea→setEnabled(FALSE);

$workTimeHourField→setEnabled(FALSE);

$workTimeMinutesField→setEnabled(FALSE);

$commentTextArea→setEnabled(FALSE);

$nextStepsTextArea→setEnabled(FALSE);

}

$this→htmlCode = „<div id='ArbeitsjournalEditor' >“;

foreach ($this→widgets as $widget) {

$this→htmlCode .= $widget;

}

$this→htmlCode .= „</div>“;


}

/**

* Stellt eine Tabelle aus den Tasks eines Arbeitsjournals zusammen. Wenn der

* entsprechende Wochenbericht signiert wurde, sind die Daten in der Tabelle nicht

* editierbar, ansonsten schon.

*

* @param type $journal : Das Journal dessen Tasks in der Tabelle angezeigt werden

* soll.

*

* @return \RIOTable

*/

public static function createTasksTable ($journal) {

$tasksFilter = [];

$tasksFilter[Task::ARBEITSJOURNAL] = $journal→get(Arbeitsjournal::ID);

$tasks = RIOPersister::getInstance()→getList(Task::class, $tasksFilter);

$tasktable = new RIOTable(Task::class, $tasks);

$tasktable→enableFiltering(false);

$tasktable→hideColumn(Task::ARBEITSJOURNAL, TRUE);

$report = $journal→get(Arbeitsjournal::WOCHENBERICHT);

$reportIsSigned = WeeklyReportControl::isReportSigned($report);

$userEqualsOwner = ($report→get(Wochenbericht::BESITZER)→get(Person::ID) === RIOPresenterHelper::getUserId());

if (!$reportIsSigned && $userEqualsOwner) {

$tasktable→setCellRenderer(Task::STUNDEN, new RIOInputTimeRenderer(12));

$tasktable→setCellRenderer(Task::MINUTEN, new RIOInputTimeRenderer(59));

$tasktable→setCellRenderer(Task::ERLEDIGTE_AUFGABEN, new RIOTextAreaRenderer(4 , 30, 120));

$tasktable→setCellRenderer(Task::ERFOLGE, new RIOTextAreaRenderer(4, 60, 240));

$tasktable→setCellRenderer(Task::SCHWIERIGKEITEN, new RIOTextAreaRenderer(4, 60, 240));

}

return $tasktable;

}

}


3.3.1.6.RIOInputTimeRenderer.php

<?php


/**

* RIOInputRenderer wird verwendet um bei RIO Table anstatt die die Werte direkt

* in die Zellen zu schreiben, ein InputFeld für Zeit anzuzeigen.

*

* Instanzierungsbeispiel: <code>new RIOInputTimeRenderer($max)</code>

*

* @author p.gabathuler

*/

class RIOInputTimeRenderer extends RIOWidget {

protected $max;

/**

* Konstruktor Methode

*

* @param int $max : der Mximalwert für den Input.

*

* @return RIOInputTimeRenderer : Der Renderer.

*/

function __construct(int $max) {

$this→max = $max;

}

/*

* Stellt das Input widget zusammen mit dem vom aufrufer vorgegebenen

* Werten.

*/

protected function initializeComponent() {

$inputField = new RIOInput($this→entity, $this→field);

$inputField→setType('number');

$inputField→setMaxLength(2);

$inputField→setMinimum(0);

$inputField→setMaximum($this→max);

$this→htmlCode = $inputField;

}


}


3.3.1.7.RIOTextAreaRenderer.php

<?php


/**

* RIOTaskTextAreaRenderer wird verwendet um bei RIOTable, anstatt die Werte

* einfach in die Zellen zu schreiben, stattdessen ein Textfeld anzuzeigen

*

* Instanzierungsbeispiel: <code>new RIOTextAreaRenderer($rows, $cols, $maxTextLength)</code>

*

* @author p.gabathuler

*/

class RIOTextAreaRenderer extends RIOWidget {

private $rows;

private $cols;

private $maxTextLength;


/**

* Konstruktor Methode

*

* @param int $rows : Anzahl Zeilen beim Textfeld

* @param int $cols : Anzahl Zeichen pro Zeile

* @param int $maxTextLength : maximal erlaubte Anzahl an Zeichen.

*

* @return RIOTextAreaRenderer

*/

public function __construct(int $rows = null, int $cols = null, int $maxTextLength = null) {

$this→rows = $rows;

$this→cols = $cols;

$this→maxTextLength = $maxTextLength;

}


/**

* Setzt das Widget mit den vom Aufrufer festgelegten Werten zusammen.

*/

protected function initializeComponent() {

$textarea = new RIOTextarea($this→entity, $this→field);

if (!is_null($this→rows)) {

$textarea→setRowAttributeTo($this→rows);

}

if (!is_null($this→cols)) {

$textarea→setColAttributeTo($this→cols);

}

if (!is_null($this→maxTextLength)) {

$textarea→setMaxTextLength($this→maxTextLength);

}

$this→htmlCode = $textarea;

}

}


3.3.1.8.RIOCustomWidget.php

<?php

/**

* Mit RIOCustomWidget lässt sich jeder beliebige html code in ein Widget verpacken.

*

* Instanzierungsbeispiel: <code>new RIOCustomWidget($html)</code>

*

* @author p.gabathuler

*/

class RIOCustomWidget extends RIOWidget {


/**

* Konstruktor Methode

*

* @param String $html :

*/

public function __construct(String $html) {

$this→htmlCode = $html;

}


/**

* wWird von RIOWidget geerbt. Wird hier nicht benötigt.

*/

protected function initializeComponent() {}


}


3.3.1.9.RIOArbeitsjournal.css

/*

autor: p.gabathuler

*/


#ArbeitsjournalEditor label {

font-weight: bold;

display: block;

margin-top: 5px;

}


#ArbeitsjournalEditor section {

display: inline-block;

margin-top: 12px;

margin-left: 4px;


}


#ArbeitsjournalEditor textArea {

display: block;

height: auto;

padding: 3px;

width: 536px;

resize: none;

overflow: overlay;

font-family: monospace;

margin-top: 5px;

}


#ArbeitsjournalEditor input {

width: 40px;

margin-left: 8px;

}


#ArbeitsjournalEditor .RIOTable tr {

height: min-content;

}


#ArbeitsjournalEditor .RIOTable th, #ArbeitsjournalEditor .RIOTable td {

min-width: 0;

max-width: none;

width: min-content;

}


#ArbeitsjournalEditor .RIOTable tr:first-child {

border-bottom: 3px solid #eeeeee;

}


#ArbeitsjournalEditor .RIOTable tr th:nth-child(1), #ArbeitsjournalEditor .RIOTable tr th:nth-child(2) {

width: 68px;

}


#ArbeitsjournalEditor .RIOTable tr th:nth-child(3) {

width: 225px;

}


#ArbeitsjournalEditor .RIOTable tr th:nth-child(4), #ArbeitsjournalEditor .RIOTable tr th:nth-child(5) {

width: 428px;

}


#ArbeitsjournalEditor .RIOTable tr td:nth-child(3) textArea {

width: 204px;

}


#ArbeitsjournalEditor .RIOTable tr td:nth-child(4) textArea, #ArbeitsjournalEditor .RIOTable tr td:nth-child(4) textArea {

width: 408px;

}


3.3.1.10.RIOContainerWeeklyReportWidget.css

/*

Author : p.gabathuler

*/


.RIOContainerWeeklyReportWidget {

width: 100%;

}


button.RIOSignReportAction {

height: 100%

}


.RIOContainerWeeklyReportWidget nav button {

border: none;

width: 15px;

height: 15px;

background-color: transparent;

font-size: 12px;

margin-left: 10px;

margin-right: 10px;

}


.RIOContainerWeeklyReportWidget .RIOCalendarWidget table {

min-width: 100%;

height: 200px;

}


.RIOContainerWeeklyReportWidgetTable {

width: 100%;

}


.RIOContainerWeeklyReportWidgetTable > tbody > tr > td:last-of-type {

width: 100%;

}


.RIOContainerWeeklyReportWidgetTable tbody td {

vertical-align: middle;

}


.RIOContainerWeeklyReportWidgetTable tbody p {

text-align: center;

font-weight: bold;

font-size: 15px;

}


.RIOContainerWeeklyReportWidgetTable tbody p.done {

color: green;

}


.RIOContainerWeeklyReportWidgetTable table {

width: 100%;

}


.RIOMessage {

padding: 15px;

margin: 15px;

width: 500px;

border: 2px solid black;

}


.WeekReportInfo {

height: 30px;

}


3.3.1.11.WeeklyReportControl.php

<?php


/**

* WeeklyReportControl bietet Funktionen an

*

* Instanzierungsbeisbiel: muss nicht instanziert werden, da alle Methoden

* statisch sind.

*

* Aufrufbeispiel: <code>WeeklyReportControl::initialiseWeeklyReport()</code>

*

* @author p.gabathuler

*/

class WeeklyReportControl {

/**

* Erstellt ein Wochenbericht und stzt aktuelles Jahr, Monat und Wochennummer

* ein. Der aktuell eingeloggte User wird als Besitzer eingetragen. persistiert

* den neu erstellten Wochenbericht. Dies alles geschieht nur wenn beim

* Besitzer für die aktuell Woche noch kein Bericht existiert.

*

* Erstellt ein Arbeitsjournal und weist es dem aktuellen Wochenbericht zu.

* setzt den aktuellen Tag ein (Tag im Monat). Dies geschieht nur wenn

* für den entsprechenden Tag noch kein Arbeitsjuornal existiert.

*/

static public function initialiseWeeklyReport() {

$userID = RIOPresenterHelper::getUserId();

$currentYear = date('Y') + 0;

$currentMonth = RIODateHelper::getMonthFromTimestamp(time());

$currentWeek = RIODateHelper::getWeekFromTimestamp(time());


$currentWeekReport = self::getWeekReport($userID, $currentYear, $currentMonth, $currentWeek);

if (is_null($currentWeekReport)) {

$newWeekReport = new Wochenbericht();

$newWeekReport→set(Wochenbericht::JAHR, $currentYear);

$newWeekReport→set(Wochenbericht::MONAT, $currentMonth);

$newWeekReport→set(Wochenbericht::WOCHENNUMMER, $currentWeek);

$newWeekReport→set(Wochenbericht::BESITZER, $userID);

RIOPersister::getInstance()→save($newWeekReport);

$currentWeekReport = $newWeekReport;

}

$dayOfMonth = date('j') + 0;

$currentWorkJournal = self::getWorkJournal($currentWeekReport, $dayOfMonth);

if (is_null($currentWorkJournal)) {

$newWorkJournal = new Arbeitsjournal();

$newWorkJournal→set(Arbeitsjournal::WOCHENBERICHT, $currentWeekReport);

$newWorkJournal→set(Arbeitsjournal::DATUM, $dayOfMonth);

$newWorkJournal→set(Arbeitsjournal::TAGESZIEL, '');

$newWorkJournal→set(Arbeitsjournal::TOTAL_STUNDEN, 0);

$newWorkJournal→set(Arbeitsjournal::TOTAL_MINUTEN, 0);

$newWorkJournal→set(Arbeitsjournal::BEMERKUNGEN, '');

$newWorkJournal→set(Arbeitsjournal::WEITERE_ARBEITSSCHRITTE, '');

RIOPersister::getInstance()→save($newWorkJournal);

}

}

/**

* Lädt einen durch die Parameter bestimmten Wochenbericht aus der Datenbank.

*

* @param String $ownerID : Der Besitzer des Wochenberichts

*

* Es gilt der Gregorianische Kalender

* @param int $year : Das Jahr, zum Beispiel: 2017

* @param int $month : der Monat, 1-12

* @param int $week : die Nummer der Woche im Jahr, 1-53

*

* @return Wochenbericht, null wenn kein Wochenbericht für die übergebenen

* Parameter existiert.

*/

static public function getWeekReport(String $ownerID, int $year, int $month, int $week) {

$weeklyReportFilter = [];

$weeklyReportFilter[Wochenbericht::BESITZER] = $ownerID;

$weeklyReportFilter[Wochenbericht::JAHR] = $year;

$weeklyReportFilter[Wochenbericht::MONAT] = $month;

$weeklyReportFilter[Wochenbericht::WOCHENNUMMER] = $week;

$wochenberichtList = RIOPersister::getInstance()→getList(Wochenbericht::class, $weeklyReportFilter);

$wochenberichtCount = count($wochenberichtList);

if ($wochenberichtCount > 1) {

error_log(„Mehr als ein Wochenbericht für die selbe Woche existieren bei User $ownerID.“);

return $wochenberichtList[0];

} elseif ($wochenberichtCount === 0) {

return null;

} else {

return $wochenberichtList[0];

}

}

/**

* Lädt das durch die Parameter bestimmte Arbeitsjournal aus der Datenbank.

*

* @param Wochenbericht $weekReport : der Wochenbericht zu dem das Arbeitjournal

* zugeordnet sein soll

* @param int $dayOfMonth : der Tag im Monat (1-31)

*

* @return Arbeitsjournal, null wenn kein Arbeitsjournal für die übergebenen

* Parameter existiert.

*/

public static function getWorkJournal(Wochenbericht $weekReport, int $dayOfMonth) {

$workJournalFilter = [];

$workJournalFilter[Arbeitsjournal::WOCHENBERICHT] = $weekReport;

$workJournalFilter[Arbeitsjournal::DATUM] = $dayOfMonth;


$workJournalList = RIOPersister::getInstance()→getList(Arbeitsjournal::class, $workJournalFilter);

$workJournalCount = count($workJournalList);

if ($workJournalCount > 1) {

error_log(„Für den Tag $dayOfMonth im Wochenbericht $weekReport existieren mehr als ein Arbeitsjournal.“);

return $workJournalList[0];

} elseif ($workJournalCount === 0) {

return null;

} else {

return $workJournalList[0];

}

}

/**

* Dient zum Anzeigen das ein Arbeitsjournal existiert oder nicht, für ein

* bestimmtes Datum im RIOCalendarWidget.

*

* Es gilt der Gregorianische Kalender

* @param int $year : Das Jahr, zum Beispiel: 2017

* @param int $month : der Monat, 1-12

* @param int $week : die Nummer der Woche im Jahr, 1-53

* @param int $day : der Tag im Monat (1-31)

*

* @return string : „Arbeitjournal“ wenn ein Arbeitjournal für

* die übergebenen Parameter existiert, sonst ein leerer string

*/

public static function showJournal(int $year, int $month, int $week, int $day) {

$owner = $_SESSION[Wochenbericht::class .„Owner“];

$report = self::getWeekReport($owner, $year, $month, $week);

if (is_null($report)) {

return „“;

} else {

$journal = self::getWorkJournal($report, $day);

if (is_null($journal)) {

return „“;

} else {

$validator = new RIOValidateArbeitsjournal();

$_SESSION[RIOSaveJournalAction::class] = TRUE;

$class = '';

if ($validator→validate($journal)) {

$class = 'class=„done“';

}

return „<p $class>“ .Arbeitsjournal::class .'</p>';

}

}

}

/**

* Stellt die RIOShowWorkJournalAction bereit wenn für die angegebenen

* Parameter ein Journal existiert. Dabei wird auch der besitzer des

* aktuell angezeigten Wochenberichts berücksitigt.

*

* Es gilt der Gregorianische Kalender

* @param int $year : Das Jahr, zum Beispiel: 2017

* @param int $month : der Monat, 1-12

* @param int $week : die Nummer der Woche im Jahr, 1-53

* @param int $day : der Tag im Monat (1-31)

*

* @return string | \RIOShowWorkJournalAction

*/

public static function getShowWorkJournalAction(int $year, int $month, int $week, int $day) {

$owner = $_SESSION[Wochenbericht::class .„Owner“];

$report = self::getWeekReport($owner, $year, $month, $week);

if (is_null($report)) {

return „“;

} else {

$journal = self::getWorkJournal($report, $day);

if (is_null($journal)) {

return „“;

} else {

$dateString = „$day.$month.$year“;

return new RIOShowWorkJournalAction($journal→get(Arbeitsjournal::ID), $dateString);

}

}

}

/**

* Überprüft ob das Arbeitsjournal des aktuellen Tages des Benutzers ausgefüllt wurde.

* Falls nicht wird eine Meldung mit der Aufforderung dies zu machen angezeigt.

*

* @return boolean

*/

public static function checkIfCurrentWorkJournalIsDone() {

$validator = new RIOValidateArbeitsjournal();

$curenntReport = WeeklyReportControl::getWeekReport(RIOPresenterHelper::getUserId(),

date('Y') + 0, RIODateHelper::getMonthFromTimestamp(time()),

RIODateHelper::getWeekFromTimestamp(time()));


if (is_null($curenntReport)) {

return true;

}

$currentJournal = WeeklyReportControl::getWorkJournal($curenntReport, date('j') + 0);

if (is_null($currentJournal)) {

return true;

}

$_SESSION[RIOSaveJournalAction::class] = TRUE;

if (!$validator→validate($currentJournal)) {

$button = new RIOButton(new RIOShowWorkJournalAction($currentJournal→get(Arbeitsjournal::ID), date('j.n.Y')));

$logoutButton = new RIOButton(new RIOLogoutAction(TRUE));

$message = 'Ihr Arbeitsjournal dieses Tages ist noch nicht ausgefüllt.<br />'

. 'Bitte füllen Sie es aus.<br />' . $button→getHTMLCode()

. '<br />' . $logoutButton→getHTMLCode();

echo ActionHypervisor::actionError('RIO', $message);

$_SESSION[RIOSaveJournalAction::class] = FALSE;

return false;

} else {

return true;

}

}

/*

* Überprüft alle Wochenberichte des aktuell eingeloggten Users und

* Stellt eine Meldung bereit, falls nicht alle Wochenberichte signiert sind.

* Der aktuelle Wochenbericht wird dabei nicht berücksigtigt, ausser es

* ist Freitag.

*

* Die Meldung Listet alle unsignierten Wochenberichte auf.

*

* @return String Meldung | null wenn alle Wochenberichte signiert wurden.

*/

public static function checkIfThereAreUnsignedReports() {

$ownerID = RIOPresenterHelper::getUserId();

$weeklyReportFilter = [];

$weeklyReportFilter[Wochenbericht::BESITZER] = $ownerID;

$wochenberichtList = RIOPersister::getInstance()→getList(Wochenbericht::class, $weeklyReportFilter);

$AreAllSigned = TRUE;

$unsignedReportsMessage = null;

$currentYear = date('Y') + 0;

$currentMonth = RIODateHelper::getMonthFromTimestamp(time());

$currentWeek = RIODateHelper::getWeekFromTimestamp(time());

$userID = RIOPresenterHelper::getUserId();

$isFriday = (date('N') === 5);


$currentWeekReport = self::getWeekReport($userID, $currentYear, $currentMonth, $currentWeek);

$unsignedReportsString = '';

foreach ($wochenberichtList as $report) {

if (!self::isReportSigned($report) && ($report !== $currentWeekReport || $isFriday)) {

$AreAllSigned = false;

$week = $report→get(Wochenbericht::WOCHENNUMMER);

$month = RIODateHelper::getMonthName($report→get(Wochenbericht::MONAT));

$year = $report→get(Wochenbericht::JAHR);

$unsignedReportsString .= „Wochenbericht von der Woche $week im $month $year:<br />“;

$journalMesage = self::areAllJournalOfReportFilled($report);

if (!is_null($journalMesage)) {

$unsignedReportsString .= $journalMesage.'<br />';

}

}

}

if (!$AreAllSigned) {

$unsignedReportsMessage = '<div class=„RIOMessage“><h4>RIO Nachricht:</h4><br />'

. 'Folgende Ihrer Wochenbericht sind noch'

. ' nicht ageschlossen, d.h. signiert worden:<br /><br />';

$unsignedReportsMessage .= $unsignedReportsString. '<br />';

$unsignedReportsMessage .= 'Bitte füllen Sie die entsprechenden'

. ' Arbeitsjournale aus und signieren sie den Wochenbericht.<br />';

}

return $unsignedReportsMessage;

}

/**

* Überprüft ob alle Arbeitsjournale des angegebenen Wochenbericht korrekt

* ausgefüllt wurden. Stellt eine Nachricht für den Benutzer zusammen falls

* dies nicht der Fall sein sollte.

*

* @param Wochenbericht $report Der Wochenbericht dessen Arbeitsjournale

* überprüft werden sollen.

* @return string Nachricht | null wenn alle journale korrekt ausgefüllt sind.

*/

public static function areAllJournalOfReportFilled(Wochenbericht $report) {

$year = $report→get(Wochenbericht::JAHR);

$week = $report→get(Wochenbericht::WOCHENNUMMER);

$month = $report→get(Wochenbericht::MONAT);

$firstDayOfWeek = RIODateHelper::getFirstDayOfWeek($year, $week) + 0;

$journalValidator = new RIOValidateArbeitsjournal();

$allJournalsFilled = true;

$message = null;

$messageParts = '';

$_SESSION[RIOSaveJournalAction::class] = TRUE;

for ($i = $firstDayOfWeek; $i ⇐ $firstDayOfWeek + 7; $i++) {

$journal = self::getWorkJournal($report, $i);

if (!is_null($journal)) {

if (!$journalValidator→validate($journal)) {

$allJournalsFilled = false;

$messageParts .= „<div>Das Arbeitsjournal vom $i.$month.$year “

. „ist noch nicht korrekt ausgefüllt: <br />“;

$messageParts .= $journalValidator→getMessage() . ''

. new RIOButton(new RIOShowWorkJournalAction($journal→get(Arbeitsjournal::ID), „$i.$month.$year“))

.'<br /><br /><div>';

}

}

}

$_SESSION[RIOSaveJournalAction::class] = FALSE;

if (!$allJournalsFilled) {

$message = $messageParts;

}

return $message;

}

/**

* Überprüft ob ein Wochenbericht von dessen Besitzer und seinem Vorgesetzten

* signiert wurde.

*

* @param type $report : Der fragliche Wochenbericht

* @return boolean

*/

public static function isReportSigned(Wochenbericht $report) {

return !is_null($report→get(Wochenbericht::SIGNATURE_BESITZER)) &&

!is_null($report→get(Wochenbericht::SIGNATURE_VORGESETZTER));

}

/**

* Überprüft, ob der eingeloggte User als

* Fachvorgesetzter der Übergebenen Person eingetragen ist.

*

* @param Person $reportOwner : Der Besitzer eines bestimmten Wochenberichts

*

* @return boolean : beantwortet die Frage, ob der eingeloggte User als

* Fachvorgesetzter des Wochenberichtsbesitzers eingetragen ist.

*/

public static function isUserBossOfReportOwner(Person $reportOwner) {

$bossFilter = [];

$bossFilter[Tag::LEFT_ENTITY] = $reportOwner;

$bossFilter[Tag::RIGHT_ENTITY] = RIOPresenterHelper::getUserId();

$tags = RIOPersister::getInstance()→getList(Tag::class, $bossFilter);

$isCurrentUserBossOfOwner = false;

if (count($tags) > 0) {

foreach ($tags as $tag) {

if ($tag→get(Tag::ENUM)→get(Enum::DESCRIPTION) === „Fachvorgesetzter“) {

$isCurrentUserBossOfOwner = true;

break;

}

}

}

return $isCurrentUserBossOfOwner;

}


}


3.3.1.12.RIODeleteTaskAction.js

/* global ActionHypervisor */


/**

* Client Teil der Action zum löschen von Tasks.

* Löst die entsprechende serverseitige Aktion aus.

*

* Instanzierungsbeispiel: <code>new RIODeleteTaskAction()</code>

*

* @author p.gabathuler

*/

var RIODeleteTaskAction = function RIODeleteTaskAction() {

this.perform();

};


/**

* Hier wird die eigentliche Aktion ausgelöst. Per Ajax wird die ID des

* zu löschenden Tasks an den PHP Teil der Aktion übermittelt.

*/

RIODeleteTaskAction.prototype.perform = function () {

var self = this;


// die ID des Zu löschenden Tasks wird ermittelt.

var taskTR = $('#ArbeitsjournalEditor .RIOTable tr').filter('.Selected');

if (taskTR === undefined) {

return;

}

var taskID = taskTR.attr('id');

$.ajax({

url: „action/ActionHypervisor.php“,

method: 'POST',

data: {

action: self.constructor.name,

taskID: taskID

},

success: function (data) {

if(ActionHypervisor.hasError(data)) {

return;

}

// gelöschtes Element entfernen

taskTR.remove();

}

});

};


3.3.1.13.RIODeleteTaskAction.php

<?php


/**

* RIODeleteTaskAction löscht den übertmittelten Task aus der Datenbank und

* stellt die Tasktabelle ohne gelöschtes Element zusammen.

*

* Instanzierungsbeispiel: <code>new RIODeleteTaskAction()</code>

*

* @author p.gabathuler

*/

class RIODeleteTaskAction extends RIOAction {

/*

* Konstrukor Methode

*

* @return RIODeleteTaskAction

*/

public function __construct() {

$this→setText('löschen');

$this→setTooltip('Löscht den markierten Task');

$this→javaScriptObject = „new “ . self::class . „();“;

}

/*

* Die eigentliche Aktion

*/

public function perform() {

$taskID = filter_input(INPUT_POST, 'taskID', FILTER_SANITIZE_STRING);

$journal = RIOPersister::getInstance()→load(RIOPersister::getInstance()→load($taskID)→get(Task::ARBEITSJOURNAL));

RIOPersister::getInstance()→delete($taskID);

echo RIOWorkJournalWidget::createTasksTable($journal);

}


}




3.3.1.14.RIONewTaskAction.js


/* global ActionHypervisor */


/**

* Client Teil der Action zu erstellen eines neuen Tasks für ein bestimmtes

* Arbeitsjournal. Stellt die aktualisierte Tasktabelle mit dem neuen Task dar.

*

* Instanzierungsbeispiel: <code>new RIONewTaskAction(journalId)</code>

*

* @param String journalId : Die Arbeitsjournal ID des Journal zu dem

* der Task gehören wird.

*

* @author p.gabthuler

*/

var RIONewTaskAction = function RIONewTaskAction(journalId) {

this.journalId = journalId;

this.perform();

};


/**

* Die eigentliche Aktion

*/

RIONewTaskAction.prototype.perform = function () {

var self = this;


$.ajax({

url: „action/ActionHypervisor.php“,

method: 'POST',

data: {

action: self.constructor.name,

journalID: self.journalId


},

success: function (data) {

if(ActionHypervisor.hasError(data)) {

return;

}


$('.RIODialog .RIOTable tbody').append($(data).find('tr').last());

makeTasksSelectable();

addOnChangeHandlerToInputs();

}

});

};


3.3.1.15.RIONewTaskAction.php

<?php


/**

* RIONewTaskAction erstellt ein neues Task Objekt für ein bestimmtes Arbeitsjournal.

* Stellt die aktualisierte Tasktabelle zur Anzeige bereit. Maximal können 7

* Task einem Journal zugeordnet werden.

*

* @param String : Die ID des Arbeitjournals zu dem der Task gehören wird.

*

* Instanzierungsbeispiel: <code>new RIONewTaskAction($journalId)</code>

*

* @author p.gabathuler

*/

class RIONewTaskAction extends RIOAction {

/*

* Konstrukor Methode

*

* @param String : Die ID des Arbeitjournals zu dem der Task gehören wird.

*

* @return RIODeleteTaskAction

*/

public function __construct(String $journalID = null) {

$this→setText('neuer Task');

$this→setTooltip('neuen Task hinzufügen');

$this→javaScriptObject = „new “ . self::class . „('$journalID');“;

}

/*

* Die eigentliche Aktion

*

*/

public function perform() {

$journalID = filter_input(INPUT_POST, „journalID“, FILTER_SANITIZE_STRING);

$tasksFilter = [];

$tasksFilter[Task::ARBEITSJOURNAL] = $journalID;

$tasks = RIOPersister::getInstance()→getList(Task::class, $tasksFilter);

if (count($tasks) < 7) {

$newTask = new Task();

$newTask→set(Task::ARBEITSJOURNAL, $journalID);

$newTask→set(Task::STUNDEN, 0);

$newTask→set(Task::MINUTEN, 0);

$newTask→set(Task::ERLEDIGTE_AUFGABEN, '');

$newTask→set(Task::ERFOLGE, '');

$newTask→set(Task::SCHWIERIGKEITEN, '');

RIOPersister::getInstance()→save($newTask);

$tasks[] = $newTask;

$tasktable = RIOWorkJournalWidget::createTasksTable(RIOPersister::getInstance()→load($journalID));


echo $tasktable→getHTMLCode();

} else {

ActionHypervisor::actionError(self::class, „Maximal sind 7 Task für ein Arbeitsjournal erlaubt.“);

}

}


}




3.3.1.16.RIOLogoutAction.js

/* ActionHypervisor */


/**

* Client Teil der Aktion zum Ausloggen aus RIO.

*

* Instanzierungsbeispiel: <code>new RIOLogoutAction()</code>

*

* @author p.gabathuler

*/

var RIOLogoutAction = function RIOLogoutAction(ignoreCheck) {

this.ignoreCheck = ignoreCheck ;

this.perform();

};


/**

* Der serverseitige Teil der Aktion wird ausgelöst.

* Schickt der Serverteil true wird die Seite refreshed. Damit wird wieder die

* Login Ansicht angezeigt.

*

*/

RIOLogoutAction.prototype.perform = function () {

var self = this;


$.ajax({

url: „action/ActionHypervisor.php“,

method: 'POST',

data: {

action: self.constructor.name,

ignoreCheck: this.ignoreCheck

},

success: function (data) {

if(ActionHypervisor.hasError(data)) {

return;

}

location.reload();

}

});

};


3.3.1.17.RIOSaveJournalAction.js

/* global ActionHypervisor */


/**

* Löst Aktion zum speichern der Daten, die ins Arbeitsjournal eingetragen wurden, aus.

*

* Instanzierungsbeispiel: <code>new RIOSaveJournalAction(journalId)</code>

*

* @param String journalId : Die Arbeitsjournal ID

*

* @author p.gabathuler

*/

var RIOSaveJournalAction = function RIOSaveJournalAction(journalId) {

this.journalId = journalId;

this.perform();

};


/*

* Liest die zu speichernden Journal- und zugehörigen

* Task-Daten aus dem DOM aus und schickt sie im JSON Format an den serverseitigen

* Teil der Applikation.

*/

RIOSaveJournalAction.prototype.perform = function () {

var self = this;

var journalFieldNames = [„Tagesziel“, „TotalStunden“, „TotalMinuten“, „Bemerkungen“, „WeitereArbeitsschritte“];

var journalData = new Object();

for (var i = 0; i < journalFieldNames.length; i++) {

journalData[journalFieldNames[i]] = $(„#“ + self.journalId + „_“ + journalFieldNames[i]).html();

}

var jsonJournalData = JSON.stringify(journalData);

var taskFieldNames = [„Stunden“, „Minuten“, „ErledigteAufgaben“, „Erfolge“, „Schwierigkeiten“];

var tasksData = new Object();

$(„.RIODialog .RIOTable tbody tr“).each(function(index, element) {

var taskId = $(element).attr('id');

tasksData[taskId] = new Object();

for (var i = 0; i < taskFieldNames.length; i++) {

tasksData[taskId][taskFieldNames[i]] = $(„#“ + taskId + „_“ + taskFieldNames[i]).html();

}

});

var jsonTaskData = JSON.stringify(tasksData);


$.ajax({

url: „action/ActionHypervisor.php“,

method: 'POST',

data: {

action: self.constructor.name,

journalID: self.journalId,

journalData: jsonJournalData,

taskData: jsonTaskData

},

success: function (data) {

if(ActionHypervisor.hasError(data)) {

return;

}


}

});

};


3.3.1.18.RIOSaveJournalAction.php

<?php


/**

* RIOSaveJournalAction speichert die per POST übermittelten Daten

* ins angegebene Arbeitsjournal, auch die zu Journal gehörigen Task-Daten

* werden gespeichert.

*

* Instanzierungsbeispiel: <code>new RIOSaveJournalAction($journalID)</code>

*

* @param String : die ID des Arbeitsjournals dessen Daten bearbeitet wurden.

*

* @author p.gabathuler

*/

class RIOSaveJournalAction extends RIOAction {

/**

* Konstruktor Methode

*

* @param String $journalID

*/

public function __construct(String $journalID = null) {

$this→setText('Speichern');

$this→setTooltip('Speichert das Arbeitsjournal inklusive Tasks.');

$this→javaScriptObject = „new “ . self::class . „('$journalID');“;

}

/**

* Die eigentliche Aktion

*/

public function perform() {

$journalID = filter_input(INPUT_POST, „journalID“, FILTER_SANITIZE_STRING);

$journalData = filter_input(INPUT_POST, „journalData“);

$taskData = filter_input(INPUT_POST, „taskData“);


$journalDataArray = json_decode($journalData, true);

$taskDataArray = json_decode($taskData, true);

$journal = RIOPersister::getInstance()→load($journalID);

$journal→set(Arbeitsjournal::TAGESZIEL, $journalDataArray[Arbeitsjournal::TAGESZIEL]);

$journal→set(Arbeitsjournal::TOTAL_STUNDEN, $journalDataArray[Arbeitsjournal::TOTAL_STUNDEN]);

$journal→set(Arbeitsjournal::TOTAL_MINUTEN ,$journalDataArray[Arbeitsjournal::TOTAL_MINUTEN]);

$journal→set(Arbeitsjournal::BEMERKUNGEN ,$journalDataArray[Arbeitsjournal::BEMERKUNGEN]);

$journal→set(Arbeitsjournal::WEITERE_ARBEITSSCHRITTE ,$journalDataArray[Arbeitsjournal::WEITERE_ARBEITSSCHRITTE]);

$_SESSION[RIOSaveJournalAction::class] = TRUE;

RIOPersister::getInstance()→save($journal);

foreach ($taskDataArray as $taskId ⇒ $taskValue) {

$task = RIOPersister::getInstance()→load($taskId);

$task→set(Task::STUNDEN, $taskValue[Task::STUNDEN]);

$task→set(Task::MINUTEN, $taskValue[Task::MINUTEN]);

$task→set(Task::ERLEDIGTE_AUFGABEN, $taskValue[Task::ERLEDIGTE_AUFGABEN]);

$task→set(Task::ERFOLGE, $taskValue[Task::ERFOLGE]);

$task→set(Task::SCHWIERIGKEITEN, $taskValue[Task::SCHWIERIGKEITEN]);

RIOPersister::getInstance()→save($task);

}

$_SESSION[RIOSaveJournalAction::class] = FALSE;

}


}




3.3.1.19.RIOShowWorkJournalAction.js

/* global ActionHypervisor */


/**

* Client Teil der Aktion zum Anzeigen eines bestimmten Arbeitsjournals.

*

* Instanzierungsbeispiel: <code>new RIOShowWorkJournalAction(journalId, date)</code>

*

* @param String journalId : Die ID des Arbeitsjournal dessen Daten angezeigt werden

* sollen.

*

* @param String date: Datum zum Anzeigen im Titel des Dialogs

*

* @author p.gabathuler

*/

var RIOShowWorkJournalAction = function RIOShowWorkJournalAction(journalId, date) {

this.journalId = journalId;

this.date = date;

this.perform();

};


/**

* Die eigentliche Aktion.

* Öffnet ein Dialog Fenster und zeig darin das Arbeitjuornal Widget an.

*

*/

RIOShowWorkJournalAction.prototype.perform = function () {

var self = this;

var dialog = new RIODialog();

dialog.setTitle('Arbeitsjournal vom ' + self.date);

dialog.setBlocking(true);


$.ajax({

url: „action/ActionHypervisor.php“,

method: 'POST',

data: {

action: self.constructor.name,

journalID: self.journalId


},

success: function (data) {

if(ActionHypervisor.hasError(data)) {

return;

}

dialog.setMessage(data);

dialog.show();

dialog.maximize();

makeTasksSelectable();

addOnChangeHandlerToInputs();

}

});

};




3.3.1.20.RIOShowWorkJournalAction.php

<?php


/**

* Serverseitiger Teil zum Anzeigen eines bestimmten Arbeitsjournals.

*

* Instanzierungsbeispiel: <code>new RIOShowWorkJournalAction($journalID, $dateString)</code>

*

* @param String : die ID des Arbeitsjournals dessen Daten angezeigt werden sollen.

*

* @author p.gabathuler

*/

class RIOShowWorkJournalAction extends RIOAction {

/**

* Konstruktor Methode

*

* @param String $journalID : die ID des Arbeitsjournals dessen Daten angezeigt werden sollen.

* @param String $dateString : Das Datum für das das Journal gültig ist im anzuzeigenden Format.

*

* @return RIOShowWorkJournalAction

*/

public function __construct(String $journalID = null, String $dateString = null) {

$this→setText('zum Arbeitsjournal');

$this→setTooltip('öffnet das Arbeitsjournal');

$this→javaScriptObject = „new “ . self::class . „('$journalID', '$dateString');“;

}

/**

* Die eigentliche Aktion.

*/

public function perform() {

$journalID = filter_input(INPUT_POST, „journalID“, FILTER_SANITIZE_STRING);

$journalWidet = new RIOWorkJournalWidget($journalID);

echo $journalWidet→getHTMLCode();

}


}


3.3.1.21.RIOLogoutAction.php


<?php


/**

* Aktion zum Ausloggen aus der RIO Webseite.

*

* Instanzierungsbeispiel: <code>new RIOLogoutAction()</code>

*

* @author p.gabathuler

*/

class RIOLogoutAction extends RIOAction {

/**

* Konstruktor Methode

*

* @return RIOLogoutAction : Die Aktion

*/

function __construct(bool $ignoreCheck = false) {

$this→text = 'Abmelden';

$this→tooltip = 'Meldet Sie von RIO ab.';

$this→javaScriptObject = „new “ . self::class . '('.($ignoreCheck ? 'true' : 'false'). ');';

}


/**

* Die Session des Users wird gelöscht, damit kann der User kann Aktionen

* mehr auf RIO ausführen. Dies geschieht nur wenn das Arbeitsjournal des

* Tages ausgefüllt ist.

*/

public function perform() {

$ignoreCheck = filter_input(INPUT_POST, „ignoreCheck“, FILTER_VALIDATE_BOOLEAN);

if($ignoreCheck || WeeklyReportControl::checkIfCurrentWorkJournalIsDone()) {

session_destroy();

}

}

}


3.3.1.22.RIOSignReportAction.js

/* global ActionHypervisor */


/**

* Client Teil der Aktion zum Signieren von einem bestimmten Wochenbericht.

*

* Instanzierungsbeispiel: <code>new RIOSignReportAction()</code>

*

* @author p.gabathuler

*/

var RIOSignReportAction = function RIOSignReportAction() {

this.perform();

};


/**

* Die eigentliche Aktion.

* Löst den serverseitigen Teil der Aktion aus.

*/

RIOSignReportAction.prototype.perform = function () {

var self = this;


$.ajax({

url: „action/ActionHypervisor.php“,

method: 'POST',

data: {

action: self.constructor.name

},

success: function (data) {

if(ActionHypervisor.hasError(data)) {

return;

}


}

});

};


3.3.1.23.RIOSignReportAction.php

<?php


/**

* Aktion zum signieren eines bestimmten Wochenberichts.

* Der Wochenbericht lässt sich nur signieren wenn alle seine Arbeitsjournale

* korrekt ausgefüllt sind.

*

* Instanzierungsbeispiel: <code>new RIOSignReportAction()</code>

*

* @author p.gabathuler

*/

class RIOSignReportAction extends RIOAction {

/**

* Konstruktor Methode

*

* @return RIOSignReportAction Die Aktion zum signieren eines Bestimmten

* Wochenberichts.

*/

public function __construct() {

$this→setText('Wochenbericht Signieren');

$this→setTooltip('Wochenbericht wird von Ihnen Signiert');

$this→javaScriptObject = „new “ . self::class . „();“;

}

/*

* Signiert den aktuell angezeigten Wochenbericht. Die Info welcher Wochenbericht

* das ist, wird aus der Session geholt.

*

* Nur der Besitzer des Wochenberichts kann für sich signieren.

* Die Fachvorgesetzten können den bericht auch signieren Für ihre Signatur

* gibt es ein seperates Attribut.

*/

public function perform() {

if ($_SESSION['CalendarWidget']['activeDisplay'] !== RIOCalendarWidget::MONTH_WEEK_DISPLAY) {

return;

}


$owner = $_SESSION[Wochenbericht::class .„Owner“];

$year = $_SESSION['CalendarWidget']['year'];

$month = $_SESSION['CalendarWidget']['month'];

$week = $_SESSION['CalendarWidget']['week'];

$report = WeeklyReportControl::getWeekReport($owner, $year, $month, $week);

$currentUserID = RIOPresenterHelper::getUserId();

$reportOwner = $report→get(Wochenbericht::BESITZER);

$message = WeeklyReportControl::areAllJournalOfReportFilled($report);

if ($currentUserID === $reportOwner→get(Person::ID)) {

if (!is_null($message)) {

ActionHypervisor::actionError('RIO Nachricht', $message);

return;

}

$report→set(Wochenbericht::SIGNATURE_BESITZER, RIOPresenterHelper::getUserId());

RIOPersister::getInstance()→save($report);

} elseif (WeeklyReportControl::isUserBossOfReportOwner($reportOwner)) {

if (!is_null($message)) {

ActionHypervisor::actionError('RIO Nachricht', $message);

return;

}

$report→set(Wochenbericht::SIGNATURE_VORGESETZTER, RIOPresenterHelper::getUserId());

RIOPersister::getInstance()→save($report);

} else {

ActionHypervisor::actionError('RIO Nachricht', „Sie sind nicht berechtigt, diesen Bericht zu signieren.“);

}

}


}


3.3.1.24.WeeklyReportSkripts.js

/**

* Hängt einen Mouse klick listener an die tr Elemente der Tasktabelle

* damit man sie selektieren kann. Das selektierte Element wird per CSS

* hervorgehoben.

*/

function makeTasksSelectable () {

$('#ArbeitsjournalEditor .RIOTable tbody tr').click(

function (event) {

$('#ArbeitsjournalEditor .RIOTable tbody tr').removeClass('Selected');

$(event.target).parents('tr').addClass('Selected');

}

);

};


/**

* Hängt einen changelistener an die Inputs des RIOWorkJournalWidgets

* Dies ist nötig damit, z.B. bei Chrome auch mit den Pfeiltasten

* veränderte Werdte persistiert werden können.

*

*/

function addOnChangeHandlerToInputs() {

$('#ArbeitsjournalEditor input').each(function (index, element) {

var name = $(element).attr('name');

$(element).change(function () {

$('#' + name).html($(element).val());

});

});

}


3.3.1.25.RIOValidateTask.php

?php


/**

* Validiert die Integrität der Task Daten

*

* Instanzierungsbeispiel: <code>new RIOValidateTask()</code>

*/

class RIOValidateTask extends RIOValidate {


/**

* Validiert die Integrität der Task Daten

*

* @param Entity $entity : die zu überprüfende Entität

* @return boolean : validierung erfolgreich oder nicht

*/

public function validate(Entity $entity) {

if (!$this→checkClassType($entity, Task::class)) {

$this→setMessage(„Interner Fehler. “);

return false;

}

$dataArray = $entity→getDataArray();

$validated = true;

$this→setMessage('');

if ($dataArray[Task::STUNDEN] < 0 || $dataArray[Task::STUNDEN] > 12) {

$this→addMessage('Bei den Tasks sind bei Stunden nur die Werte 0-12 erlaubt. <br />');

$validated = false;

}

if ($dataArray[Task::MINUTEN] < 0 || $dataArray[Task::MINUTEN] > 59) {

$this→addMessage('Bei den Tasks sind bei Minuten nur die Werte 0-59 erlaubt. <br />');

$validated = false;

}

if (strlen($dataArray[Task::ERLEDIGTE_AUFGABEN]) > 120) {

$this→addMessage('Das Textfeld erledigte Aufgaben bei den Tasks darf maximal 120 Zeichen enthalten. <br />');

$validated = false;

}

if (isset($_SESSION[RIOSaveJournalAction::class]) && $_SESSION[RIOSaveJournalAction::class] && strlen($dataArray[Task::ERLEDIGTE_AUFGABEN]) < 15) {

$this→addMessage('Das Textfeld erledigte Aufgaben bei den Tasks muss mindestens 15 Zeichen enthalten. <br />');

$validated = false;

}

if (strlen($dataArray[Task::ERFOLGE]) > 240) {

$this→addMessage('Das Textfeld '. Task::ERFOLGE .' bei den Tasks darf maximal 240 Zeichen enthalten. <br />');

$validated = false;

}

if (strlen($dataArray[Task::SCHWIERIGKEITEN]) > 240) {

$this→addMessage('Das Textfeld '. Task::SCHWIERIGKEITEN .' bei den Tasks darf maximal 240 Zeichen enthalten. <br />');

$validated = false;

}

return $validated;

}


}




3.3.1.26.RIOValidateArbeitsJournal.php

<?php


/**

* Validiert die Integrität der Arbeitsjournal Daten

*

* Instanzierungsbeispiel: <code>new RIOValidateArbeitsjournal()</code>

*/

class RIOValidateArbeitsjournal extends RIOValidate {


/**

* Validiert die Integrität der Arbeitsjournal Daten

*

* @param Entity $entity : die zu überprüfende Entität

* @return boolean : validierung erfolgreich oder nicht

*/

public function validate(Entity $entity) {

if (!$this→checkClassType($entity, Arbeitsjournal::class)) {

$this→setMessage('Interner Fehler: Unerwarteter Parameter erhalten. ');

return false;

}

$dataArray = $entity→getDataArray();

$validated = true;

$this→setMessage('');

if (strlen($dataArray[Arbeitsjournal::TAGESZIEL]) > 560) {

$this→addMessage('Das Textfeld ' .Arbeitsjournal::TAGESZIEL . ' darf maximal 560 Zeichen enthalten. <br />');

$validated = false;

}

if (isset($_SESSION[RIOSaveJournalAction::class]) && $_SESSION[RIOSaveJournalAction::class] && strlen($dataArray[Arbeitsjournal::TAGESZIEL]) < 40) {

$this→addMessage('Das Textfeld ' .Arbeitsjournal::TAGESZIEL . ' muss mindestens 40 Zeichen enthalten. <br />');

$validated = false;

}

if ($dataArray[Arbeitsjournal::TOTAL_STUNDEN] < 0 || $dataArray[Arbeitsjournal::TOTAL_STUNDEN] > 12) {

$this→addMessage('Bei Total Stunden sind nur die Werte 0-12 erlaubt. <br />');

$validated = false;

}

if ($dataArray[Arbeitsjournal::TOTAL_MINUTEN] < 0 || $dataArray[Arbeitsjournal::TOTAL_MINUTEN] > 59) {

$this→addMessage('Bei Total Minuten sind nur die Werte 0-59 erlaubt. <br />');

$validated = false;

}

if (strlen($dataArray[Arbeitsjournal::BEMERKUNGEN]) > 560) {

$this→addMessage('Das Textfeld ' .Arbeitsjournal::BEMERKUNGEN . ' darf maximal 560 Zeichen enthalten. <br />');

$validated = false;

}

if (strlen($dataArray[Arbeitsjournal::WEITERE_ARBEITSSCHRITTE]) > 560) {

$this→addMessage('Das Textfeld ' .Arbeitsjournal::WEITERE_ARBEITSSCHRITTE . ' darf maximal 560 Zeichen enthalten. <br />');

$validated = false;

}

if (isset($_SESSION[RIOSaveJournalAction::class]) && $_SESSION[RIOSaveJournalAction::class] && strlen($dataArray[Arbeitsjournal::WEITERE_ARBEITSSCHRITTE]) < 15) {

$this→addMessage('Das Textfeld ' .Arbeitsjournal::WEITERE_ARBEITSSCHRITTE . ' muss mindestens 15 Zeichen enthalten. <br />');

$validated = false;

}

return $validated;

}



}


3.3.1.27.RIOValidateWochenbericht.php

<?php


/**

*

*/

class RIOValidateWochenbericht extends RIOValidate {


public function validate(Entity $entity) {

if (!$this→checkClassType($entity, Wochenbericht::class)) {

$this→setMessage(„Interner Fehler “ . __LINE__ . „ “ . __FILE__);

return false;

}

return true;

}


}




3.3.2.Angepasste Dateien

Die Änderungen an schon bestehenden Dateien wird mit folgender Farbe markiert: Beispielmarkierung

3.3.2.1.ActionHypervisor.php

<?php


session_start();


include_once '../autoloader.php';


new ActionHypervisor();


/**

* ActionHypervisor wird aufgerufen, wenn irgendeine Aktion durchzuführen ist.

* (Der Aufruf geschieht z.B. mittels AJAX, z.B. von einem Button aus.)

* ActionHypervisor erwartet einen Usernamen und eine Aktion.

* Der Username wird verifiziert (falls nötig abgefragt)

* Ist das OK, wird die Aktion instanziert, durchgeführt und der Vorgang geloggt.

*

* @author J.Windmeisser

*

*/

class ActionHypervisor {


private static $personGroups = null;


const USER = 'user';

const TICKET = 'ticket';

const ACTION = 'action';


public function __construct() {


$user = filter_input(INPUT_POST, self::USER, FILTER_SANITIZE_STRING);

$ticket = filter_input(INPUT_POST, self::TICKET, FILTER_SANITIZE_STRING);

$action = filter_input(INPUT_POST, self::ACTION, FILTER_SANITIZE_STRING);

$logMessage = date('[D F j G:i:s Y]') . „, Zeitstempel: “ . time() . „, Userid: “ . (key_exists(Person::ID, $_SESSION) ? RIOPresenterHelper::getUserId() : „no User“) . „, Aktion: “ .$action . PHP_EOL;

$logfileName = '../log/RIOlog_'.date(„j.n.Y“).'.txt';

file_put_contents($logfileName, $logMessage, FILE_APPEND);


/** die Action wird zugelassen, wenn

* a) es ein Login ist

* b) es der eingeloggte User verlangt

*/

if ($action == RIOLoginAction::class or $action == RIOShowPasswordResetPresenterAction::class or RIOSendPasswordResetLinkAction::class or RIOPresenterHelper::checkUserId($user)) {

// $logginEntity = $actionObject→getLoggingEntity();

// $logginEntity→set(Entity::USER, $user);

// $logginEntity→set(LogEntry::ACTION, $action);

if ($action == RIOEntityDeleteAction::class) {

RIOEntityDeleteFactory::deleteEntity();

} else {

$actionObject = new $action();

$actionObject→perform();

}

} else {

ActionHypervisor::actionError(self::class, 'Willst du uns auf den Arm nehmen?<br /><br />Session abgebrochen,\nUser fotografiert: '. $user);

//$abbruchmeldung = '<script type=„text/javascript“>alert(„Willst du uns auf den Arm nehmen?\n\nSession abgebrochen,\nUser fotografiert“);window.location=„/rio/index.php“</script>';

//RIOPresenterHelper::outputData($abbruchmeldung);

exit();

}

}


public static function actionError($class, $message, $fatal = false) {

$error = '<ERROR>';

$error .= '<CLASS>';

$error .= $class . '</CLASS><MESSAGE> ';

$error .= $message . '.';

$error .= '</MESSAGE></ERROR>';

echo $error;

if ($fatal) {

exit();

}

}


public static function getGroups(Person $person) {

if (self::$personGroups !== null) {

return self::$personGroups;

}

$rootGroupId = RIOEntityConstant::ENUM_USER_GROUP;

$rootGroup = RIOPersister::getInstance()→load($rootGroupId);

$groups = $rootGroup→getDescendants();


$groupList = [];


foreach ($groups as $group) {

$filter[ManyToMany::LEFT_ENTITY] = $group;

$filter[ManyToMany::RIGHT_ENTITY] = $person;

$personGroups = RIOPersister::getInstance()→getList(ManyToMany::class, $filter);

foreach ($personGroups as $personGroup) {

$groupList[] = $personGroup→get(ManyToMany::LEFT_ENTITY);

}

}


self::$personGroups = $groupList;

return $groupList;

}


public static function operationsAllowed() {

return '111';

$person['Enum_2E1EC9ADC178A0D9E93F59705423AABA'] = '100';

$user = RIOPersister::getInstance()→load(RIOPresenterHelper::getUserId());

$groups = self::getGroups($user);


foreach ($groups as $group) {

if (isset($person[„$group“])) {

return '111';

return str_replace('0', '', $person[„$group“]);

}

}


return null;

}


}


3.3.2.2.RIOCalendarWidget.php

<?php


/**

* Description of RIOCalendarWidget

*

* @author j.windmeisser, p.gabathuler

*/

class RIOCalendarWidget extends RIOWidget {


const YEAR_DISPLAY = 'YearDisplay';

const MONTH_DISPLAY = 'MonthDisplay';

const MONTH_WEEK_DISPLAY = 'MonthWeekDisplay';

const WEEK_DAY_DISPLAY = 'WeekDayDisplay';

const DAY_DISPLAY = 'DayDisplay';


private $display;

private $year;

private $month;

private $week;

private $day;

private $renderer;

private $weeklyReportControlEnabled = false;


public function __construct($renderer = null, $display = self::MONTH_WEEK_DISPLAY, $year = null, $month = null, $week = null, $day = null) {


$this→year = $year + 0;

if (!isset($year)) {

$this→year = date('Y') + 0;

}


$this→month = $month + 0;

if (!isset($month) || $month > 12 || $month < 1) {

$this→month = date('m') + 0;

}


$this→week = $week + 0;

if (!isset($week) || $week > 53 || $week < 0) {

$this→week = date('W') + 0;

}


$this→day = $day + 0;

if (!isset($day) || $day > 31 || $day < 1) {

$this→day = date('d') + 0;

}


$this→display = $display;

$this→renderer = $renderer;

}


protected function initializeComponent() {

$this→htmlCode = '';

$this→htmlCode .= '<section class=„' . self::class . '“ data-display=„' . $this→display . '“ data-year=„' . $this→year . '“ data-month=„' . $this→month . '“ data-week=„' . $this→week . '“>';

if ($this→weeklyReportControlEnabled && $this→display === self::MONTH_WEEK_DISPLAY) {

$this→htmlCode .= '<div class=„WeekReportInfo“ >' . $this→displayWeekReportInfo(). '</div>' ;

}

$this→htmlCode .= $this→getNavigation($this→year, $this→month, $this→week, $this→day);

$this→htmlCode .= $this→getDisplay($this→display, $this→year, $this→month, $this→week, $this→day);

$this→htmlCode .= '</section>';

}


protected function getDisplay($display, $year, $month, $week, $day) {


$code = '';

if ($this→weeklyReportControlEnabled) {

$_SESSION['CalendarWidget'] = [];

$_SESSION['CalendarWidget']['activeDisplay'] = $display;

$_SESSION['CalendarWidget']['year'] = $year;

$_SESSION['CalendarWidget']['month'] = $month;

$_SESSION['CalendarWidget']['week'] = $week;

}


switch ($display) {

case self::YEAR_DISPLAY:

$code .= $this→showYear($year);

break;

case self::MONTH_DISPLAY:

$code .= $this→showMonth($year, $month);

break;

case self::MONTH_WEEK_DISPLAY:

$code .= $this→showWeekMonth($year, $month, $week);

break;

case self::WEEK_DAY_DISPLAY:

$code .= $this→showWeekDay($year, $month, $week);

break;

default:

$code .= 'ERROR';

}


return $code;

}


protected function getNavigation($year, $month, $week, $day) {

$title = $year;


if ($month > 0 && ($this→display === self::MONTH_DISPLAY || $this→display === self::MONTH_WEEK_DISPLAY)) {

$monthObj = DateTime::createFromFormat('!m', $month);

$title .= ' ' . $monthObj→format('F');

}


if ($week >= 0 && $this→display === self::MONTH_WEEK_DISPLAY) {

$title .= ' Week ' . $week;

}



$goBackAction = new RIOCalendarWidgetShowAction($this→weeklyReportControlEnabled);

$code = '<nav>';

if ($this→display !== self::YEAR_DISPLAY) {

$code .= '<button ' . $this→getGoBackAction($year, $month, $week, $day) . ' onclick=„' . $goBackAction . '“ type=„button“>&#9651;</button>';

}

$code .= '<button ' . $this→getNavigationAction($year, $month, $week, $day, true) . ' onclick=„' . $goBackAction . '“ type=„button“>&#9665;</button>';

$code .= '<span>';

$code .= $title;

$code .= '</span>';

$code .= '<button ' . $this→getNavigationAction($year, $month, $week, $day, false) . ' onclick=„' . $goBackAction . '“ type=„button“>&#9655;</button>';

$code .= '</nav>';


return $code;

}


protected function showYear($year) {

$code = '<div class=„' . self::class . 'YearDisplay Display“>';

$code .= '<canvas data-year=„' . $year . '“ class=„' . self::class . 'GraphOverlay“>';

$code .= '</canvas>';

$code .= '<table>';

$code .= '<thead>';

$code .= '<tr>';

for ($m = 1; $m ⇐ 12; ++$m) {

$class = '';

if (RIODateHelper::isToday($year, $m)) {

$class = 'class=„Title Today“ style=„width: 8.333%“';

}

$month = RIODateHelper::getMonthName($m);

$code .= '<th ' . $class . '>';

$code .= $month;

$code .= '</th>';

}

$code .= '</tr>';

$code .= '</thead>';

$code .= '<tbody>';

$code .= '<tr>';

$monthAction = new RIOCalendarWidgetShowAction($this→weeklyReportControlEnabled);

for ($m = 1; $m ⇐ 12; ++$m) {

$class = '';

if (RIODateHelper::isToday($year, $m)) {

$class = 'class=„Today“';

}

$code .= '<td ' . $class . ' data-year=„' . $year . '“ data-month=„' . $m . '“ ondblclick=„' . $monthAction . '“ data-display=„' . self::MONTH_DISPLAY . '“>';

$code .= '</td>';

}

$code .= '</tr>';

$code .= '</tbody>';

$code .= '</table>';

$code .= '</div>';


return $code;

}


protected function showMonth($year, $month) {

$code = '<div class=„' . self::class . 'MonthDisplay Display“>';

$code .= '<canvas id=„' . $year . '“ class=„' . self::class . 'GraphOverlay“>';

$code .= '</canvas>';

$code .= '<table>';

$code .= '<thead>';


$code .= '<tr>';

$code .= '<th>';

$code .= '</th>';

for ($day = 1; $day ⇐ 7; ++$day) {

$code .= '<th>' . RIODateHelper::getDayName($day) . '</th>';

}

$code .= '</tr>';

$code .= '</thead>';

$code .= '<tbody>';

$firstWeek = RIODateHelper::getFirstWeekOfMonth($year, $month);

$lastWeek = RIODateHelper::getWeekCountForMonth($year, $month) + $firstWeek;

$weekCounter = 0;

//echo $firstWeek . ':' . $lastWeek . ';';

for (; $firstWeek < $lastWeek; ++$firstWeek) {

// $code .= '<tr><td>';

$code .= $this→showWeekDay($year, $month, $firstWeek, $weekCounter);

++$weekCounter;

// $code .= '</td></tr>';

}


$code .= '</tbody>';


$code .= '</table>';

$code .= '<div>';


return $code;

}


protected function showWeekMonth($year, $month, $week, $weekCounter = -1) {

$code = '<div class=„' . self::class . 'WeekDisplay Display“>';

if ($this→display === self::MONTH_WEEK_DISPLAY) {

$code .= '<canvas id=„' . $year . '“ class=„' . self::class . 'GraphOverlay“>';

$code .= '</canvas>';

}

$code .= '<table>';

$code .= '<thead>';


$code .= $this→showWeekDay($year, $month, $week, $weekCounter);


$code .= '</tbody>';

$code .= '</table>';

$code .= '<div>';


return $code;

}


protected function checkYearBoundary($year, $month, $week, $day) {

$monthCorrected = $this→checkMonthBoundary($year, $month, $week, $day);


if ($monthCorrected > $month && $month == 1) {

return $year - 1;

} else if ($monthCorrected < $month && $month == 12) {

return $year + 1;

}


return $year;

}


protected function checkMonthBoundary($year, $month, $week, $day) {

$firstWeekOfMonth = RIODateHelper::getFirstWeekOfMonth($year, $month);

if ($week == $firstWeekOfMonth && $day ⇐ 31 && $day > 7) {

if ($month == 1) {

return 12;

}

return $month - 1;

}


$lastWeekOfMonth = RIODateHelper::getLastWeekOfMonth($year, $month);

if ($week == $lastWeekOfMonth && $day ⇐ 6) {

if ($month == 12) {

return 1;

}

return $month + 1;

}


return $month;

}


protected function showWeekDay($year, $month, $week, $weekCounter = -1) {


$monthAction = '';

if ($this→display === self::MONTH_DISPLAY) {

$monthAction = new RIOCalendarWidgetShowAction($this→weeklyReportControlEnabled);

}

$code = '<tr>';

$code .= '<th>';

for ($weekDay = 1; $weekDay < 8; ++$weekDay) {

$monthDay = RIODateHelper::getMonthDayNumber($year, $week, $weekDay);

$monthCorrected = $this→checkMonthBoundary($year, $month, $week, $monthDay);

$yearCorrected = $this→checkYearBoundary($year, $month, $week, $monthDay);

if (RIODateHelper::isToday($yearCorrected, $monthCorrected, $monthDay)) {

$class = 'class=„Title Today“';

} else {

$class = $this→getWeekDayClass($year, $month, $week, $monthDay, $weekDay, $weekCounter, 'Title');

}

if ($this→display === self::MONTH_WEEK_DISPLAY) {

$monthDay = RIODateHelper::getDayName($weekDay) . ' ' . $monthDay;

}

$code .= '<td ' . $class . '>' . $monthDay . '</td>';

}

$code .= '</th>';

$code .= '</tr>';


if ($this→display === self::MONTH_WEEK_DISPLAY) {

$code .= '</thead>';

$code .= '<tbody>';

}


$code .= '<tr>';

$code .= '<th>';

$code .= '<span>' . $week . '</span>';

$code .= '</th>';

for ($weekDay = 1; $weekDay < 8; ++$weekDay) {

$monthDay = RIODateHelper::getMonthDayNumber($year, $week, $weekDay);

$monthCorrected = $this→checkMonthBoundary($year, $month, $week, $monthDay);

$yearCorrected = $this→checkYearBoundary($year, $month, $week, $monthDay);

$class = $this→getTodayClass($yearCorrected, $monthCorrected, $monthDay);

$weekCorrected = $week;

if ($week >= 52 || $week ⇐ 1) {

$weekCorrected = RIODateHelper::getCorrectedWeekNumberInBetweenYears($yearCorrected, $monthCorrected);

}


$title = 'title=„' . RIODateHelper::getHoliday($monthCorrected, $monthDay) . '“';

//$class = $this→getWeekDayClass($year, $month, $week, $weekDay, $monthDay, $weekCounter);

$code .= '<td ' . $title . ' ' . $class . ' data-year=„' . $yearCorrected . '“ data-month=„' . $monthCorrected . '“ data-week=„' . $weekCorrected . '“ '

. 'ondblclick=„' . (($this→weeklyReportControlEnabled && $this→display === self::MONTH_WEEK_DISPLAY) ? WeeklyReportControl::getShowWorkJournalAction($yearCorrected, $monthCorrected, $weekCorrected, $monthDay) : $monthAction)

. '“ data-display=„' . self::MONTH_WEEK_DISPLAY . '“>';

if ($this→weeklyReportControlEnabled) {

$code .= WeeklyReportControl::showJournal($yearCorrected, $monthCorrected, $weekCorrected, $monthDay);

}

$code .= '</td>';

}

$code .= '</tr>';


return $code;

}


protected function getTodayClass($year, $month, $day) {

$class = '';

if (RIODateHelper::isToday($year, $month, $day)) {

$class = 'class=„Today“';

}


return $class;

}


protected function getWeekDayClass($year, $month, $week, $monthDay, $weekDay, $weekCounter, $class = '') {

$firstDayOfTheWeek = RIODateHelper::getFirstDayOfWeek($year, $week);

$lastDayOfTheWeek = RIODateHelper::getLastDayOfWeek($year, $week);

$lastDayMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year);

$holiday = RIODateHelper::getHoliday($month, $monthDay);


if ($this→display == self::MONTH_DISPLAY) {

if ($monthDay < 32 && $monthDay > 7 && $weekCounter == 0) {

$class = 'class=„' . $class . ' OutOfMonth“';

} else if ($monthDay < $lastDayMonth - 7 && $firstDayOfTheWeek > $lastDayOfTheWeek && $weekCounter > 0) {

$class = 'class=„' . $class . ' OutOfMonth“';

} else if ($weekDay > 5) {

$class = 'class=„' . $class . ' Weekend“';

} else if (RIODateHelper::getHoliday($month, $monthDay) != null) {

$class = 'class=„' . $class . ' Holiday“';

} else if ($class !== '') {

$class = 'class=„' . $class . '“';

}

}


if ($this→display == self::MONTH_WEEK_DISPLAY) {

$firstWeekInMonth = RIODateHelper::getFirstWeekOfMonth($year, $month);

$lastWeekOfMonth = RIODateHelper::getLastWeekOfMonth($year, $month);

if ($firstDayOfTheWeek > $lastDayOfTheWeek && $monthDay ⇐ 31 && $monthDay > 7 && $week == $firstWeekInMonth) {

$class = 'class=„' . $class . ' OutOfMonth“';

} else if ($firstDayOfTheWeek > $lastDayOfTheWeek && $monthDay >= 1 && $monthDay ⇐ 7 && $week == $lastWeekOfMonth) {

$class = 'class=„' . $class . ' OutOfMonth“';

} else if ($weekDay > 5) {

$class = 'class=„' . $class . ' Weekend“';

} else if (RIODateHelper::getHoliday($month, $monthDay) != null) {

$class = 'class=„' . $class . ' Holiday“';

} else if ($class !== '') {

$class = 'class=„' . $class . '“';

}

}


return $class;

}


protected function getGoBackAction($year, $month, $week, $day) {

switch ($this→display) {

case self::DAY_DISPLAY:

return ' data-renderer=„' . $this→renderer . '“ data-display=„' . self::MONTH_WEEK_DISPLAY . '“ data-year=„' . $year . '“ data-month=„' . $month . '“ data-week=„' . $week . '“ ';

case self::MONTH_WEEK_DISPLAY:

return ' data-renderer=„' . $this→renderer . '“ data-display=„' . self::MONTH_DISPLAY . '“ data-year=„' . $year . '“ data-month=„' . $month . '“ ';

case self::MONTH_DISPLAY:

return ' data-renderer=„' . $this→renderer . '“ data-display=„' . self::YEAR_DISPLAY . '“ data-year=„' . $year . '“ ';

}

}


protected function getNavigationAction($year, $month, $week, $day, $reverse) {

switch ($this→display) {

case self::DAY_DISPLAY:

$day = $reverse ? –$day : ++$day;

return ' data-renderer=„' . $this→renderer . '“ data-display=„' . self::DAY_DISPLAY . '“ data-year=„' . $year . '“ data-month=„' . $month . '“ data-week=„' . $week . '“ data-day=„' . $day . '“ ';

case self::WEEK_DAY_DISPLAY:

break;

case self::MONTH_WEEK_DISPLAY:

$firstDayOfWeek = RIODateHelper::getFirstDayOfWeek($year, $week);

$lastDayOfWeek = RIODateHelper::getLastDayOfWeek($year, $week);


if ($reverse) {

if ($month ⇐ 1 && $week ⇐ 1 && ($firstDayOfWeek > $lastDayOfWeek || $firstDayOfWeek == 1 )) {

$month = 12;

$year–;

$week = RIODateHelper::getLastWeekOfMonth($year, $month);

} else if ($week == RIODateHelper::getFirstWeekOfMonth($year, $month)) {

$month–;

$week = RIODateHelper::getLastWeekOfMonth($year, $month);

} else {

$week–;

}

} else {

if ($month >= 12 && $week >= 52 && ($firstDayOfWeek > $lastDayOfWeek || $lastDayOfWeek == 31 )) {

$month = 1;

$year++;

$week = RIODateHelper::getFirstWeekOfMonth($year, $month);

if ($week >= 52) {

//$week = RIODateHelper::getFirstWeekOfMonth($year, $month);

}

} else if ($week == RIODateHelper::getLastWeekOfMonth($year, $month)) {

$month++;

if ($month > 12) {

$month = 12;

$week = 53;

} else {

$week = RIODateHelper::getFirstWeekOfMonth($year, $month);

}

} else {

$week++;

}

}


return ' data-renderer=„' . $this→renderer . '“ data-display=„' . self::MONTH_WEEK_DISPLAY . '“ data-year=„' . $year . '“ data-month=„' . $month . '“ data-week=„' . $week . '“ ';

case self::MONTH_DISPLAY:

$month = $reverse ? –$month : ++$month;

if ($month < 1) {

$month = 12;

–$year;

}

if ($month > 12) {

$month = 1;

++$year;

}

return ' data-renderer=„' . $this→renderer . '“ data-display=„' . self::MONTH_DISPLAY . '“ data-year=„' . $year . '“ data-month=„' . $month . '“ ';

case self::YEAR_DISPLAY:

$year = $reverse ? –$year : ++$year;

return ' data-renderer=„' . $this→renderer . '“ data-display=„' . self::YEAR_DISPLAY . '“ data-year=„' . $year . '“ ';

}

}

/**

* Schaltet WeklyReportControl beim Kalender ein oder aus.

* Wenn es eingeschaltet ist, wird bei der Wochen- und Monatsansicht,

* wenn ein Arbeitsjournal für den Tag existiert, bei diesem Tag

* angezeigt, dass eines existiert. Dann kann man auch bei

* der Wochenansicht mit doppelklick auf den Tag das entsprechende

* Arbeitsjournal öffnen.

*

* @param bool $boolean

*/

public function enableWeeklyReportControl(bool $boolean) {

$this→weeklyReportControlEnabled = $boolean;

}

/**

* Gibt den Status des aktuell angezeigten Wochenberichts zurück.

* Wenn der Wochenbericht noch nciht signiert wurde und der User dazu

* berechtigt ist zu signieren wird ein Button zum signieren des Wochenberichts

* angezeigt.

*

* @return string : Status des aktuell angezeigten Wochenberichts

*/

private function displayWeekReportInfo() {

$ownerID = $_SESSION[Wochenbericht::class .„Owner“];

$report = WeeklyReportControl::getWeekReport($ownerID, $this→year, $this→month, $this→week);

if (is_null($report)) {

return '<p>Für diese Woche ist kein Wochenbericht vorhanden.<p>';

} else if(WeeklyReportControl::isReportSigned($report)){

return '<p>Der Wochenbericht wurde signiert und ist damit abgeschlossen.<p>';

} else {

$reportOwner = RIOPersister::getInstance()→load($ownerID);

if ($ownerID === RIOPresenterHelper::getUserId() ||

WeeklyReportControl::isUserBossOfReportOwner($reportOwner)) {

return (new RIOButton(new RIOSignReportAction($ownerID)))→getHTMLCode();

} else {

return '<p>Der Wochenbericht wurde noch nicht signiert.<p>';

}

}

}

}


3.3.2.3.RIOCalendarWidgetShowAction.js

/* global ActionHypervisor */


var RIOCalendarWidgetShowAction = function RIOCalendarWidgetShowAction(event, weeklyReportControlEnabled) {

this.user = $('meta[name=user]').attr(„content“);

this.ticket = $('#Ticket').html();


this.event = event;

this.renderer = $(event.target).data('renderer');

this.display = $(event.target).data('display');

this.year = $(event.target).data('year');

this.month = $(event.target).data('month');

this.week = $(event.target).data('week');

this.day;

this.weeklyReportControlEnabled = weeklyReportControlEnabled ? true : false;


if (undefined === this.year) {

this.year = null;

}


if (undefined === this.month) {

this.month = null;

}


if (undefined === this.week) {

this.week = null;

}


this.container = $(event.target).closest('.RIOCalendarWidget');


this.perform(true);

};


RIOCalendarWidgetShowAction.prototype.perform = function (internalCall) {

var self = this;


$.ajax({

url: „action/ActionHypervisor.php“,

method: 'POST',

data: {

action: self.constructor.name,

user: self.user,

ticket: self.ticket,

renderer: self.renderer,

display: self.display,

year: self.year,

month: self.month,

week: self.week,

day: self.day,

weeklyReportControlEnabled: this.weeklyReportControlEnabled

},

success: function (data) {

if(ActionHypervisor.hasError(data)) {

return;

}

var selectedTreeItem = $(self.event.target).closest('.RIOContainerPerformanceWidget').find('.RIOEnumTreeWidget').find('.Selected');

self.container.replaceWith(data);

selectedTreeItem.click();

}

});

};

3.3.2.4.RIOCalendarWidgetShowAction.php

<?php


/**

* Description of RIOCalendarWidgetShowWeekAction

*

* @author j.windmeisser

*/

class RIOCalendarWidgetShowAction extends RIOAction {


public function __construct(bool $weeklyReportControlEnabled = false) {

$this→javaScriptObject = 'new ' . self::class . '(event, '.$weeklyReportControlEnabled.');';

}


public function perform() {

$renderer = filter_input(INPUT_POST, 'renderer', FILTER_SANITIZE_STRING);

$display = filter_input(INPUT_POST, 'display', FILTER_SANITIZE_STRING);

$year = filter_input(INPUT_POST, 'year', FILTER_VALIDATE_INT);

$month = filter_input(INPUT_POST, 'month', FILTER_VALIDATE_INT);

$week = filter_input(INPUT_POST, 'week', FILTER_VALIDATE_INT);

$weeklyReportControlEnabled = filter_input(INPUT_POST, 'weeklyReportControlEnabled', FILTER_VALIDATE_BOOLEAN);


$calendar = new RIOCalendarWidget($renderer, $display, $year, $month, $week);

$calendar→enableWeeklyReportControl($weeklyReportControlEnabled);


echo $calendar;

}


}


3.3.2.5.RIOInput.php

<?php


/**

* Description of RIOInput

*

* @author j.windmeisser, p.gabathuler

*/



class RIOInput extends RIOWidget {


protected $hidden = '';

protected $type = '';

protected $maxlength = '';

protected $min = '';

protected $max = '';


public function __construct(Entity $entity, String $fieldName) {

$this→entity = $entity;

$this→field = $fieldName;

}


protected function initializeComponent() {

$operations = ActionHypervisor::operationsAllowed();

if ($operations !== null) {

$uniqueFieldName = '';

if ($operations >= '11') {

$uniqueFieldName = Entity::getUniqueFieldId($this→entity, $this→field);

$this→htmlCode = '<div style=„display:none“ id=„' . $uniqueFieldName . '“ class=„' . self::CLASS_INPUT_FIELD . ' HighlightPanel“>' . $this→entity→get($this→field) . '</div>'

. '<input ' . $this→type . $this→maxlength . $this→min .$this→max

.' onkeyup=„$(\'#' . $uniqueFieldName . '\').html($(this).val());“ ' . $this→disabled . ' placeholder= „' . $this→field . '„class=“' . $this→field . ' ' . self::CLASS_INPUT_FIELD . ' ' . self::CLASS_SEARCHABLE . ' Hide“ ' . $this→hidden . ' name=\'' . $uniqueFieldName . '\' value=\'' . $this→entity→get($this→field) . '\'/>';

} else {

if (!$this→hidden) {

$this→htmlCode = '<span class=„' . self::CLASS_INPUT_FIELD . ' ' . self::CLASS_SEARCHABLE . '“ ' . $this→hidden . '>' . $this→entity→get($this→field) . '</span>';

}

}

}

}


public function setHidden(bool $hidden) {

$this→hidden = '';

if ($hidden) {

$this→hidden = 'type=„hidden“';

}

}

/**

* Setzt beim beim Input den Typ fest.

*

* @param String $type : der Typ des Inputs (z.B. 'number' oder 'text')

*/

public function setType(String $type) {

$this→type = „ type='$type' “;

}

/**

* Setzt beim Input ein Minimum für den Wert fest.

*

* min und max Attribute funktionieren nur bei folgenden Input Typen:

* number, range, date, datetime, datetime-local, month, time und week.

*

* @param int $min : Minimum

*/

public function setMinimum(int $min) {

$this→min = „ min='$min' “;

}

/**

* Setzt beim Input ein Maximum für den Wert fest.

*

* min und max Attribute funktionieren nur bei folgenden Input Typen:

* number, range, date, datetime, datetime-local, month, time und week.

*

* @param int $max : Maximum

*/

public function setMaximum(int $max) {

$this→max = „ max='$max' “;

}

/**

* Setzt beim Input die maximale Anzahl zeichen fest, die akzeptiert wird.

*

* @param int $maxLength : das Maximum erlauber Zeichen.

*/

public function setMaxLength(int $maxLength) {

$this→maxlength = „ maxlength='$maxLength' “;

}

}


3.3.2.6.RIONavigationPresenter.php

<?php


/**

* TestPresenter ist applikationsspezifisch.

*

*

* @author Andreas Fischer, j.windmeisser, p.gabathuler

* @copyright © RAFISA GmbH, Seestrasse 78, CH-8703 Erlenbach-Zürich, Telefon: ++41-44-912 18 08, Telefax: ++41-44-912 18 09

*/

class RIONavigationPresenter implements IntRIOPresenter {

private $messages = '';


public function getHTMLCode() {

// Wenn jetzt die vorbereiteten Komponenten angezeigt werden sollen

// (in unserem Fall Toolbar und zwei Tabellen)

// dann stellen wir mal den HTML-Code bereit:

$logoutButton = new RIOButton(new RIOLogoutAction());

$actionBackAction = new RIOActionBackAction();

$actionBackButton = new RIOButton($actionBackAction);

$entitySelector = new RIOEntitySelectorWidget();

$code = $logoutButton→getHTMLCode();

$code .= $entitySelector→getHTMLCode();

$code .= $actionBackButton;

$code .= '<div id=„DataView“>';

$code .= $this→messages;

$code .= '</div>';

return $code;

}


public function setEditable($editable) {

}


public function setVisible($visible) {

}

public function addMessage ($message) {

$this→messages .= $message . '<br /><br />';

}


}


3.3.2.7.RIOPersonPresenter.php

<?php


/**

* Description of RIOPersonPresenter

*

* @author j.windmeisser, p.gabathuler

*/

class RIOPersonPresenter extends RIOWidget {


public function __construct(Person $person) {

$this→entity = $person;

}


protected function initializeComponent() {

$personEditor = new RIOEntityEditor($this→entity);


$filterTitle[Enum::TYPE] = RIOEntityConstant::ENUM_TYPE_TITLE;

$comboboxRenderer = new RIOCombobox($this→entity, Person::TITLE, $filterTitle);

$personEditor→setRenderer(Person::TITLE, $comboboxRenderer);


// $action = new RIOOpenLocalFolderAction(Person::_class());

// $action→setText('OwnCloud');

// $action→setTooltip('Öffnet das OwnCloud Verzeichnis der Person');

// $openFolder = new RIOButton($action);

// $personEditor→addButton($openFolder);


$tagContainer = new RIOContainerTagWidget($this→entity);

$personContainer = new RIOContainerPersonWidget($this→entity, RIOEntityConstant::ENUM_PERSON_ROOT, '', Person::class);


$contactContainer = new RIOContainerContactWidget($this→entity, RIOEntityConstant::ENUM_CONTACT_ROOT, '', Person::class);

$addressContainer = new RIOContainerAddressWidget($this→entity);

//$groupContainer = new RIOContainerUserGroupWidget($this→entity);


$topEnum = RIOPersister::getInstance()→load(RIOEntityConstant::ENUM_PERFORMANCE_ROOT);

$performanceContainer = new RIOContainerPerformanceWidget($topEnum, $this→entity);


//$action = new DocumentOpen();

//$documentContainer = new RIOContainerDocumentWidget($this→entity, RIOEntityConstant::ENUM_DOCUMENT_ROOT, '', Dokument::class, $action);


$dynamicTabContainer = new RIODynamicTabWidget($this→entity);


$newAddress = new RIOAddressNewAction();

$dynamicTabContainer→setEditable(Person::ADDRESSES, $newAddress);


$connectPerson = new RIOPersonAddToPersonAction();

$dynamicTabContainer→setEditable(Person::PERSONEN, $connectPerson);


$newContactNote = new RIOContactNewAction($this→entity, RIOContainerContactWidget::class . '-' . $this→entity);

$dynamicTabContainer→setEditable(Person::CONTACTS, $newContactNote);


//$dynamicTabContainer→setEditable(Person::PERFORMANCE, $newPerformanceCalendar);

$weeklyReporContainer = new RIOContainerWeeklyReportWidget($this→entity);


$dynamicTabContainer→setTabWidget(Person::TAGS, $tagContainer);

$dynamicTabContainer→setTabWidget(Person::PERSONEN, $personContainer);

$dynamicTabContainer→setTabWidget(Person::ADDRESSES, $addressContainer);


$operationsAllowed = ActionHypervisor::operationsAllowed();

if ($operationsAllowed >= '1') {

$forumThreadContainer = new RIOContainerForumThreadWidget($this→entity);

$newForumThread = new RIOForumThreadNewAction();

$dynamicTabContainer→setEditable(Person::FORUM_THREADS, $newForumThread);

$forumThreadContainer→setEnabled(true);

$dynamicTabContainer→setTabWidget(Person::FORUM_THREADS, $forumThreadContainer);

}


if (ActionHypervisor::operationsAllowed() >= '11') {

$medicalContainer = new RIOContainerMedicalWidget($this→entity, RIOEntityConstant::ENUM_MEDICAL_ROOT, '', Person::class);

$newMedicalNote = new RIOContainerPersonMedicalAddNoteAction($this→entity, RIOContainerMedicalWidget::class . '-' . $this→entity);

$dynamicTabContainer→setEditable(Person::MEDICAL, $newMedicalNote);

$dynamicTabContainer→setTabWidget(Person::MEDICAL, $medicalContainer);

}


$dynamicTabContainer→setTabWidget(Person::CONTACTS, $contactContainer);

$dynamicTabContainer→setTabWidget(Person::PERFORMANCE, $performanceContainer);

$dynamicTabContainer→setTabWidget(Wochenbericht::class, $weeklyReporContainer);


if (ActionHypervisor::operationsAllowed() >= '11') {

//$dynamicTabContainer→setTabWidget('Groups', $groupContainer);

}


// Webseite wird hier zusammengebaut.

//$fileList = new RIOFileList($this→entity, Person::DATEIEN);

$this→htmlCode = '<div id=„' . __CLASS__ . $this→entity→getClass() . '“>';

//$this→htmlCode = $openFolder→getHTMLCode();

$this→htmlCode .= $personEditor;

$this→htmlCode .= $dynamicTabContainer;

//$this→htmlCode .= $documentContainer→getHTMLCode();

//$this→htmlCode .= $noteContainer;

//$this→htmlCode .= $gradeContainer→getHTMLCode();

//$this→htmlCode .= $fileList→getHTMLCode();

$this→htmlCode .= '</div>';

}


}


3.3.2.8.RIOTable.php

<?php


/**

* Eine Tabelle, wie sie im Projekt RIO häufiger vorkommt.

*

* @author a.fischer, j.windmeisser, p.gabathuler

*/

class RIOTable extends RIOWidget {


const EDIT = „edit“;

const DELETE = „delete“;

const TAG_EDITBUTTON_START = „<button id=\“[value]\„ class=\“RIOTableEditButton\„ type='button'>“;

const TAG_DELETEBUTTON_START = „<button id=\“[value]\„ class=\“RIOTableDeleteButton\„ type='button'>“;

const TAG_BUTTON_END = „</button>“;

const ID_PLACEHOLDER = „[value]“;

private $filteringEnabled = true;


private $cellRenderers = array();

private static $hiddenFields = array(

Entity::CREATED ⇒ '1',

Entity::DELETED ⇒ '1',

Entity::GUEST_RIGHT ⇒ '1',

Entity::ID ⇒ '1',

Entity::LAST_MODIFIED ⇒ '1',

Entity::LAST_USER ⇒ '1',

Entity::USER ⇒ '1',

Entity::USER_GROUP ⇒ '1',

Entity::USER_GROUP_RIGHT ⇒ '1',

Entity::USER_RIGHT ⇒ '1'

);


/**

* Die Liste/Array mit den Entities, aus denen die

* Tabelle aufgebaut wird

*

* @var $data (Array mit Entities)

*

* @see Entity.php

*/

private $data;


/**

* Enthält in der ersten Dimension die Feldnamen,

* in der zweiten einen Array mit den Definitionen, wie z.B.

* welcher Datentyp. Die zweite Dimension kann pro Feld unterschiedlich

* sein.

*

* @var $columns (Mehrdimensionales Array)

*

* @see Entity.php

*/

private $columns;


/**

* Der Datentyp/Klasse der Objekte in der Tabelle.

*

* @var $dataType

*

* @see Entity.php

*/

private $dataType;


/**

* Wird dieses Feld/Variable auf <code>true</code> gesetzt, dann werden

* Entitäten/Objekte die innerhalb eines anderes Objekts/Entität sind,

* mit all ihren Variablen angezeigt, andernfalls erscheint nur ihre Id.

*

* @var boolean $expandEntities

*/

private $expandEntities = false;


/**

* Wird dieses Feld/Variable auf <code>true</code> gesetzt, dann werden

* die Zeilen der Tabelle mit jeweils einem Editier-Button versehen.

*

* @var $editable

*/

private $editable = false;


/**

* Konstruktor um eine HTML Tabelle aufzubauen.

*

* @param string $dataType Der Datentyp/Klasse der Objekte in der Tabelle.

* @param array $data Die Liste/Array mit den Entities.

* @param array $columns Die Spaltennamen mit den Spaltendefinitionen.

*/

public function __construct($dataType, array $data = [], array $columns = []) {

$this→dataType = $dataType;

$this→columns = $columns;

$this→data = $data;

}


/**

* Diese Funktion stellt den HTML-Code der Tabelle zusammen.

* Sie lässt die Titelzeile durch @c createHeaders und die Datenzeilen

* durch @c createBody erstellen.

* Wir haben darauf geachtet, den erfoderlichen HTML-Code ohne Literals

* zu erstellen.

* Wenn @c expandEntities wahr ist, werden eingebettete Entities aufgelöst

* (also z.B. wird für eine Person die vollständige Adresse ausgewiesen

* und nicht nur deren Id)

* Wenn @c editable wahr ist, erhält die Tabelle am Ende eine zusätzliche

* Spalte mit einem Editier-Button.

*

* @see RIOWidget.php

*/

protected function initializeComponent() {

$htmlCode = '<table class=„' . self::class . '“>';

$htmlCode .= $this→createHeaders();


// und nun die Daten.

// Wir haben einen Array von Entities

$htmlCode .= $this→createBody();

$htmlCode .= RIOHTML::HTML_TABLE_END;


$tagSearch = [RIOHTML::HTML_COL_BEGIN, RIOHTML::HTML_COL_END];

$tagReplaceWith = [RIOHTML::HTML_TD_BEGIN, RIOHTML::HTML_TD_END];


if ($this→expandEntities) {

$this→htmlCode = str_replace($tagSearch, $tagReplaceWith, $htmlCode) . „<script type='text/javascript' src='RIOTable.js'></script>“;

} else {

$this→htmlCode = $htmlCode . „<script type='text/javascript' src='widget/RIOTable.js'></script>“;

}

}


public function setExpandEntities($expand) {

$this→expandEntities = $expand;

}


public function setEditable($editable) {

$this→editable = $editable;

}


private function createHeaders() {

// Titelzeile mit den Feldnamen erstellen

$dataType = $this→dataType;


$fields = $dataType::fields();

$htmlCode = RIOHTML::HTML_THEAD_BEGIN;

$htmlCode .= '<tr class=„Title“>';


if ($this→expandEntities) {

$htmlCode .= $this→createHeader($fields);

} else {

foreach ($fields as $fieldName ⇒ $fieldDefinitions) {

if (isset(self::$hiddenFields[$fieldName])) {

//$htmlCode .= '<td style=„display:none;“>' . $fieldName . '</td>';

} else {

$header = new RIOTableHeader($fieldName, $fieldDefinitions[RIOEC::TYPE]);

$htmlCode .= $header→getHTMLCode();

}

}

}

// if ($this→editable === true) {

// $htmlCode .= HTML::HTML_TH_BEGIN . self::EDIT . HTML::HTML_TH_END;

// $htmlCode .= HTML::HTML_TH_BEGIN . self::DELETE . HTML::HTML_TH_END;

// }

$htmlCode .= RIOHTML::HTML_TR_END;


$htmlCode .= RIOHTML::HTML_TR_BEGIN;


if ($this→expandEntities) {

$htmlCode .= $this→createHeader($fields);

} else {

if ($this→filteringEnabled){

foreach ($fields as $fieldName ⇒ $fieldDefinitions) {

if (isset(self::$hiddenFields[$fieldName])) {

//$htmlCode .= '<td style=„display:none;“>' . $fieldName . '</td>';

} else {

$header = new RIOTableHeaderFilter($fieldName, $fieldDefinitions[RIOEC::TYPE]);

$htmlCode .= $header→getHTMLCode();

}

}

}

}

// if ($this→editable === true) {

// $htmlCode .= HTML::HTML_TH_BEGIN . self::EDIT . HTML::HTML_TH_END;

// $htmlCode .= HTML::HTML_TH_BEGIN . self::DELETE . HTML::HTML_TH_END;

// }

$htmlCode .= RIOHTML::HTML_TR_END;


$htmlCode .= RIOHTML::HTML_THEAD_END;

return $htmlCode;

}


private function createHeader(array $fields) {

$htmlCode = „“;


foreach ($fields as $fieldName ⇒ $fieldDefinitions) {

if (isset($fieldDefinitions[RIOEC::TYPE]) && $fieldDefinitions[RIOEC::TYPE] === Entity::_class()) {

$dataType = $fieldDefinitions[RIOEC::CLASS_NAME];

$entityFields = $dataType::fields();

$htmlCode .= $this→createHeader($entityFields);

} else {

$header = new RIOTableHeader($fieldName);

$htmlCode .= $header→getHTMLCode();

}

}


return $htmlCode;

}


private function createBody() {

// Die Daten:

// Wir haben einen Array von Entities

$dataType = $this→dataType;

$fields = $dataType::fields();

$htmlCode = „\n“ . RIOHTML::HTML_TBODY_BEGIN;


foreach ($this→data as $entity) {

$ourId = $entity→get(RIOEC::ID);


$htmlCode .= str_replace(RIOHTML::HTML_REPLACE_VALUE, $ourId, RIOHTML::HTML_TR_BEGIN);


foreach ($fields as $fieldName ⇒ $fieldDefinitions) {

if (isset(self::$hiddenFields[$fieldName])) {

//$htmlCode .= '<td style=„display:none;“>' . $fieldName . '</td>';

} else {


$widget = null;

if (key_exists($fieldName, $this→cellRenderers)) {

$this→cellRenderers[$fieldName]→setValue($entity, $fieldName);

$widget = $this→cellRenderers[$fieldName];

} else {

$widget = $this→getTableRenderer($fieldDefinitions[RIOEC::TYPE], $entity, $fieldName);

$this→cellRenderers[$fieldName] = $this→getTableRenderer($fieldDefinitions[RIOEC::TYPE], $entity, $fieldName);

}

$id = Entity::getUniqueFieldId($ourId, $fieldName) . „_TD“;

$tdBegin = str_replace(RIOHTML::HTML_REPLACE_VALUE, $id, RIOHTML::HTML_TD_BEGIN);

$tdBegin = str_replace(RIOHTML::HTML_REPLACE_VALUE2, $fieldDefinitions[RIOEC::TYPE], $tdBegin);

$htmlCode .= $tdBegin . $widget→getHTMLCode() . RIOHTML::HTML_TD_END;

}

}

$htmlCode .= RIOHTML::HTML_TR_END;

}


$htmlCode .= RIOHTML::HTML_TBODY_END;


return $htmlCode;

}


public function setCellRenderer($key, IntRIOWidget $renderer) {

$this→cellRenderers[$key] = $renderer;

}


public function hideColumn($columnName, $hide) {

if ($hide) {

self::$hiddenFields[$columnName] = $hide;

} else {

unset(self::$hiddenFields[$columnName]);

}

}


/**

* Schaltet die Filterfunktionalität für die Tabelle ein oder aus.

* Wenn es eingeschaltet ist, wird im Tabellenkopf eine zusätliche

* Zeile, zum eingeben der Filterwerte für die einzelnen Spalten, angezeigt.

*

* @param bool $bool

*/

public function enableFiltering(bool $bool) {

$this→filteringEnabled = $bool;

}

}


3.3.2.9.RIOTextArea.php

<?php


/**

* Description of RIOTextarea

*

* @author j.windmeisser, p.gabathuler

*/

class RIOTextarea extends RIOWidget {

protected $placeHolderText;

protected $rows;

protected $cols;

protected $maxTextLength;


public function __construct(Entity $entity, String $fieldName) {

$this→entity = $entity;

$this→field = $fieldName;

}


protected function initializeComponent() {

$operations = ActionHypervisor::operationsAllowed();

if ($operations !== null) {

$uniqueFieldId = '';

$text = $this→entity→get($this→field);

if ($operations >= '11') {

$uniqueFieldId = Entity::getUniqueFieldId($this→entity, $this→field);

$this→htmlCode = '<div style=„display:none“ id=„' . $uniqueFieldId

. '“ class=„' . self::CLASS_INPUT_AREA . ' '

. self::CLASS_INPUT_FIELD . ' HighlightPanel“>'

. $text . '</div><textarea onkeyup=„$(\'#' . $uniqueFieldId . '\').html($(this).val());“ class=„'

. self::CLASS_INPUT_AREA . ' ' . self::CLASS_INPUT_FIELD . ' '

. self::CLASS_SEARCHABLE . ' Hide“ ' . $this→disabled

. ' rows=„' . $this→rows . '“ '

. ' cols=„' . $this→cols . '“ '

. ' maxlength=„' . $this→maxTextLength . '“ '

. ' placeholder=„' . (is_null($this→placeHolderText) ? $this→field : $this→placeHolderText). '“ '

. $this→getOnclickString() . ' name=„' . $uniqueFieldId . '“>'

. $text . '</textarea>';

} else {

$this→htmlCode = '<div class=„' . self::CLASS_INPUT_AREA . ' ' . self::CLASS_INPUT_FIELD . ' ' . self::CLASS_SEARCHABLE . '“>' . $text . '</div>';

}

}

}

/**

* Setzt den Platzhaltertext für die Textarea fest.

*

* @param String $placeHolderText : der Platzhaltertext

*/

public function setPlaceholderText(String $placeHolderText) {

$this→placeHolderText = $placeHolderText;

}


/**

* Setzt die Anzahl anzuzeigender Zeilen bei der textarea fest.

* Voraussetzung: wird vom CSS nicht übersteuert.

*

* @param int $rowCount : Anzahl Zeilen

*/

public function setRowAttributeTo(int $rowCount) {

$this→rows = $rowCount;

}

/**

* Setzt die Anzahl Zeichen pro Linie bei der Textarea fest.

*

* @param int $colCount

*/

public function setColAttributeTo(int $colCount) {

$this→cols = $colCount;

}

/**

* Setzt die maximale erlaubte Anzahl an Zeichen bei der Textarae fest.

*

* @param int $max : maximal erlaubte Anzahl Zeichen.

*/

public function setMaxTextLength (int $max) {

$this→maxTextLength = $max;

}

}




3.3.2.10.RIOValidate.php

<?php


/**

* Abstrakte Klasse die gemeinsame Logik für Validatoren bietet. Jeder Validator

* muss diese Klasse erweitern.

*

* @author Jorge Windmeisser, p.gabathuler

*/

abstract class RIOValidate implements RIOValidateInterface {

/*

* Die Mitteilung die bei einem Validations-Fehler erzeugt wird.

*/

protected $message = '';


/**

* @see ValidateInterface

*/

public function getMessage() {

return $this→message;

}


/**

* Setzt die Mitteilung die darauf hinweisen soll, was bei der Validierung

* nicht erfüllt wurde.

*

* z.B.:

*

* $this→setMessage('Der Name darf nicht länger als 255 Zeichen sein');

*

* @param string $message

*/

protected function setMessage(String $message) {

$this→message = $message;

}

/**

* Fügt der Mitteilung eine weitere hinzu.

*

* @param String $message

*/

protected function addMessage(String $message) {

$this→message .= $message;

}


/**

* Überprüft ob die übergebenen Entity vom selben Datentyp ist, wie der,

* der im zweiten Parameter übergeben wurde.

*

* Diese Funktion sollte von Subklassen, in der Funkton validate(…) als

* erstes aufgerufen werden.

*

* @param RIOEntity $entity Die Entity die überprüft werden soll.

* @param string $expectedClassType Der Datentyp als string der die Entity

* haben sollte.

* @return bool true wenn die Entity vom übergebenen Datentyp ist, sonst

* false.

*/

protected function checkClassType(Entity $entity, String $expectedClassType){

if ($entity→getClass() !== $expectedClassType) {

$this→setMessage('Die Entity ist nicht vom Typ ' . $expectedClassType . ' der Datentyp ist ' . $entity→getClass() . '.');

return false;

}


return true;

}


}



de.bkp/intern/ipa/pg2017/ipa_pg2017.txt · Zuletzt geändert: 2021/02/08 16:12 von 127.0.0.1