MSP:HÜ Aushang

Aus THM-Wiki
Wechseln zu: Navigation, Suche


Diese Wiki-Seite dient zur Dokumentation der Hausübung "Aushang", die im Rahmen des Moduls "Methoden des Softwareentwicklungsprozesses (MSP)" an der Fachhochschule Gießen-Friedberg im Wintersemester 09/10 bearbeitet wird. Verantwortlich für die Hausübung sind Barotsz Boron und Mariusz Homeniuk.

Allgemein

Mitglieder der Gruppe sind:

Boron, Bartosz
Homeniuk, Mariusz

Autoren des Moduls

  • Christian Gerhardt
  • Torsten Mehnert
  • Clemens Weiß
  • Matthias Maschler
  • Manuel Fuehrer
  • Nadja Krümmel

Lizenz

Das Modul wurde unter der GNU General Public License, version 2 veröffentlicht.


Reverse Engineering

Use-Cases

Use Case Model.jpg

Klassendiagramm

DB-Schema

Announcements db schema.png

Code-Dokumentation

Die Dokumentation wurde mithilfe des Tools Doxygen generiert.

Dokumentation

Software Metriken

Mit phpUnit erstellten Software Metriken:

metrics

CRAP-Index

Folgende Methoden sind uns bei der Analyse der Metriken aufgrund ihrer Komplexität aufgefallen:

Methode C.R.A.P - Index
Announcement::loadTemplateVariable 132
AnnounceDateSelection::setDate 132
AnnounceDateSelection::printPart 756
Edit::createAnnouncement 132

Code Coverage

Zum Zeitpunkt des Refactorings lagen keine Tests des Moduls vor, "code coverage" liegt also bei 0%. Im Zuge des Reengineerings sollen Testfälle geschrieben werden, zumindest für die neu erstellten Methoden und Funktionen.

Bad Smells

Bei der Analyse der gesammelten Metriken und einer groben Durchsicht der Quelltexte sind folgende Bad Smells aufgefallen.

Smell Ort
Large Class AnnounceDateSelection
Long Method AnnounceDateSelection::printPart
Edit::createAnnouncement
Anzeigen::showDelete
Delete::main
Long Parameter List AnnounceDateSelection::__construct
Anzeigen::show
Dead Code ziemlich alle
Switch Statement AnnounceDateSelection::get
AnnounceDateSelection::printCalender
AnnounceDateSelection::printPart
Anzeigen::showmenu
Anzeigen::getIcon

Performance-Optimierung

DB Queries

  • Bei der Erstellung der Aushänge wird für das Ermitteln des Leseprozentsatzes die Methode (aus .../announcements/classes/class.announcementdb.inc.php):
public static function getMembersInEstudy() {
   return DoctrineUser :: getNumberOfUsersInPortal();
}

bzw.

public static function getMembersInCourse($courseId) {
   return DoctrineUser :: getNumberOfUsersInCourse($courseId);
}

benutzt. Beide Methoden greifen auf die DB und liefern die Anzahl der Kurs bzw. Portal-Teilnehmer zurück. Obwohl sich diese Werte innerhalb des Prozesses der Darstellung aller Aushänge eines Kurses nicht ändern, werden die oben genannten Methoden für jeden Aushang ausgeführt. Die Optimierung bestand in der Einführung zweier globalen Variablen. Nur am Anfang des Erstellprozesses werden die entsprechenden Werte aus der DB geholt, in den globalen Variablen gespeichert und bei dem nächsten Aushang wird nur aus den globalen Variablen gelesen. Am Ende (bzw. vorm Anfang) der Erstellung aller Aushänge werden die globalen Variablen wieder geleert (bzw. mit -1 initiert):

public static function getMembersInEstudy() {
   global $tempMembersInEstudy;
   if ($tempMembersInEstudy < 0)
      $tempMembersInEstudy = DoctrineUser :: getNumberOfUsersInPortal();
   return $tempMembersInEstudy;
}
public static function getMembersInCourse($courseId) {
   global $tempMembersInCourse;
   if ($tempMembersInCourse < 0)
      $tempMembersInCourse = DoctrineUser :: getNumberOfUsersInCourse($courseId);
   return $tempMembersInCourse;
}
public static function clearMembersVars() {
   global $tempMembersInEstudy;
   global $tempMembersInCourse;
   $tempMembersInEstudy = -1;
   $tempMembersInCourse = -1;
}

Features (nach dem Walkthrough vom 19.11.09)

Lesebestätigung

Das Senden einer Lesebestätigung für einen beliebigen Aushang funktioniert nicht. Der dafür verantwortliche Code muss überarbeitet werden.

  • Bisher konnte keine Lösung gefunden werden. Code des Moduls "Announcement" sieht richtig und funktionsfähig aus. Kann an einem anderen Modul liegen.
  • Folgender Fehler wird angezeigt:

Lesebestaetigung fehler.png

Nummerierung

Die Nummerierung für der Aushänge soll Kurs- bzw. eCommunity-spezifisch erfolgen

  • Bei der Erstellung eines neuen Aushanges wird für die Nummerierung folgende Methode benutzt:
/**
* Liefert die höchste Mitteilungsnummer
*/
public static function getMaxAnnouncementNo() {
   $query = Doctrine_Query::create();
   $query = $query->select("MAX(a.int_nr) maximal");
   $query = $query->from("Announcements a");
   $retval = 0;
   $res = $query->execute();
   try {
      $res = $query->execute()->getFirst();
      $retval = $res->maximal;
   }
   catch(Exception $e) {	
      $retval = 0;
   }
   return $retval;
}
  • Hierbei wird in der Tabelle "Announcements" nach der größten bisher gespeicherten Nummer gesucht
  • Bei der Erstellung eines neuen Anhanges wird die zurückgegebene Nummer um 1 erhöht und dann als die Nummer des neuen Aushanges übernommen
  • Eine kursspezifische Nummerierung kann erreicht werden, indem die Kurs-ID des Kurses der oberen Methode mitgeteilt wird. Damit könnte man nach der größten gespeicherten Nummer eines bestimmten Kurses suchen. Da diese Nummer keine eindeutige Nummer (bezogen auf das ganze Portal) sein muss, ist die Redundanz der Nummern in verschiedenen Kursen erlaubt
  • Das Ergebnis sieht dann so aus:
/**
* Liefert die höchste Mitteilungsnummer in einem bestimmten Kurs
*/
public static function getMaxAnnouncementNo($courseid) {
   $query = Doctrine_Query::create();
   $query = $query->select("MAX(a.int_nr) maximal");
   $query = $query->from("Announcements a");
   $query = $query->where("a.course_id=?");
   $retval = 0;
   try {
      $res = $query->execute(array($courseid))->getFirst();
      $retval = $res->maximal;
   }
   catch(Exception $e) {	
      $retval = 0;
   }
   return $retval;
}

14 Tage

Bei Aushang im Foyer soll deutlicher angezeigt werden, dass es sich standardmäßig um Aushänge der letzten 14 Tage handelt.

  • Um bei nicht vorhanden Aushängen dem Benutzer deutlicher zu machen für welchen Zeitraum dies gilt, wurde die Methode showmenu() modifiziert.
  • Dabei zeigt der erste Quellcodeausschnitt die Methode so wie sie vor der Änderung aussah und der untere Quellcodeausschnitt wie sie danach bzw. jetzt aussieht.
private static function showmenu($show, $announcements) {
   $menu = new Template( PATH_TO_ROOT . "/announcement/templates/menu.html" );
   $menu_data['active0'] = "announcementMenuItem";
   $menu_data['active1'] = "announcementMenuItem";
   $menu_data['active7'] = "announcementMenuItem";
   $menu_data['active14'] = "announcementMenuItem";
   $menu_data['active'.$show] = "announcementMenuItemActive";
   $menu_data['coursename'] = AnnouncementDB::getCoursenameByID($_SESSION['course']);

   if (Count($announcements) == 0) {
   $menu_data['noannouncements'] = "block";
   $menu_data['showpdf'] = "none";
   } else {
      $menu_data['noannouncements'] = "none";
      $menu_data['showpdf'] = "block";
   }
   eval( $menu->GetTemplate() );
}
private static function showmenu($show, $announcements) {
   $menu = new Template( PATH_TO_ROOT . "/announcement/templates/menu.html" );
   $menu_data['active0'] = "announcementMenuItem";
   $menu_data['active1'] = "announcementMenuItem";
   $menu_data['active7'] = "announcementMenuItem";
   $menu_data['active14'] = "announcementMenuItem";
   $menu_data['active'.$show] = "announcementMenuItemActive";
   $menu_data['coursename'] = AnnouncementDB::getCoursenameByID($_SESSION['course']);

   if (Count($announcements) == 0) {
      $menu_data['noannouncements'] = "block";
      switch ($show) {
         case 0:
            $menu_data['noannouncements_time'] = "";
            break;
         case 1:
            $menu_data['noannouncements_time'] = "am <b>heutigen Tag</b> "; 
            break;
         case 7:
            $menu_data['noannouncements_time'] = "in den letzten <b>7 Tagen</b> ";
            break;
         case 14:
            $menu_data['noannouncements_time'] = "in den letzten <b>14 Tagen</b> ";
            break;
         }
         $menu_data['showpdf'] = "none";
      } else {
         $menu_data['noannouncements'] = "none";
         $menu_data['showpdf'] = "block";
   }

   eval( $menu->GetTemplate() );
}
  • Wie man deutlich sehen kann wurde die Array-Variable $menu_data[] um weitere mögliche Zuweisungen bzw. Inhalte ergänzt. Diese Inhalte werden je nachdem welcher Zeitraum übergeben wird entsprechend zugewiesen.
  • Durch diese Änderung und das setzen in Fettschrift des Zeitraums wird dem Benutzer nun noch deutlicher um welchem Zeitraum es sich bei nicht vorhanden Aushängen handelt.
  • Beispiel:

14tag bsp.png

Textfeldbeschränkung

Eine Textbeschränkung für den Inhalt von Aushängen soll eingeführt werden.

  • Hier schlagen wir eine Textbeschränkung von 4.294.967.295 auf 16.777.215 Zeichen vor. Die dafür erforderliche und im follgenden aufgeführte Datenbankanweisung ändert den Datentyp des Feldes "text" von LONGTEXT auf MEDIUMTEXT:
ALTER TABLE `announcements` CHANGE `text` `text` MEDIUMTEXT CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL
  • Dadurch gehen alle Zeichen eines Aushangtextes, die über diese Beschränkung von 16.777.215 liegen verloren.
  • Des Weiteren schlagen wir eine ähnliche Modifizierung für das Feld "title" vor. Der zurzeit defenierte Datentyp für das Feld "title" kann bis zu 16.777.215 Zeichen speichern, ein Wert der nach unserer Meinung auf 65.535 Zeichen beschränkt werden kann. Die nötige Datenbankanweisung lautet:
ALTER TABLE `announcements` CHANGE `title` `title` TEXT CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL
  • Auch hier gehen alle vorher gespeicherten Zeichen eines Eintrags, die über 65.535 liegen verloren bzw. werden abgeschnitten.
  • Beiden Statements wurden in die migration.sql übernommen

Bearbeitungsfunktion

Das Bearbeiten und Löschen von Aushängen soll eStudy-konform werden.

  • In der folgende Methode wird überprüft ob der angemeldete Benutzer als Admin bzw. Aushangsautor angemeldet ist. Wenn dies der Fall ist, werden bestimmte Berechtigungen zum Anzeigen der Bearbeiten- und Löschenfunktionen gesetzt. Dabei wird zwischen der standardmäßigen Anzeigesicht und einer extra Bearbeitungsicht unterschieden.
private function prepareTemplateForMode($announce_data) {
   if ($this->getMode() == "edit") {
      if ($_SESSION["usergroup"] == ADMIN || $this->getAuthor() == $_SESSION["userid"]) {
         $announce_data['display_mod'] = "block";
      } else $announce_data['display_mod'] = "none";
      $announce_data['display_lnkpdf'] = "none";
      $announce_data['display_read'] = "none";
   } else { // if its "view"
      $announce_data['display_mod'] = "none";
      $announce_data['display_lnkpdf'] = "block";
      if ($this->isRead() || $_SESSION['userid'] == $this->getAuthor()) $announce_data['display_read'] = "none";
      else $announce_data['display_read'] = "inline";
   }
   return $announce_data;
}
  • Um eine eStudy-konforme Möglichkeit zum Bearbeiten und Löschen von Aushängen zu schaffen, wurde die Methode so abgeändert, dass es nicht mehr nötig ist extra den Untermenüpunkt "Aushang bearbeiten" aufzurufen um zu den gewünschten Funktionen zu gelangen. Statt dessen stehen nun bei der standmäßigen Anzeige der Aushänge die jeweiligen Funktionen zum Bearbeiten und Löschen zu Verfügung, sobald eines der folgenden Bedigungen erfüllt ist:
    • Der angemeldete Nutzer hat Administratorrechte
    • Der angemeldete Nutzer erstellte den Aushang
 private function prepareTemplateForMode($announce_data) {  
    if ($_SESSION["usergroup"] == ADMIN || $this->getAuthor() == $_SESSION["userid"]) {
       $announce_data['display_mod'] = "block";
       $announce_data['display_lnkpdf'] = "block";
       $announce_data['display_read'] = "none";
    } else {
       $announce_data['display_mod'] = "none";
       $announce_data['display_lnkpdf'] = "block";
  if ($this->isRead() || $_SESSION['userid'] == $this->getAuthor()) $announce_data['display_read'] = "none";
   else $announce_data['display_read'] = "inline";
 }
 return $announce_data;
}
  • Die Bearbeiten und Löschfunktionen stehen nun unmittelbar im jeweiligen Aushang zu Verfügung, vorausgesetzt der Leser hat Administratorrechte oder ist selbst der jeweilige Aushangersteller.
  • Durch diese Änderung erschien der Untermenüpunkt "Aushang bearbeiten" als redundant. Der dazugehörige Eintrag wurde deshalb aus der Tabelle moduls_lang entfernt. Die dafür notwendigen Statements wurden in die migration.sql übernommen.
  • Zusätzlich wurden die vorhanden Textbuttons durch eStudy-konforme Icons ersetzt, wie es die nachfolgenden Bildschirmabzüge darstellen.

Bearbeiten icons.png

Datumsübermittlung

  • Die Übermittlung des Start- und Enddatums in die Datenbank steht nun auch bei der Bearbeitungsansicht zu Verfügung. Dafür musste die Methode updateAnnouncement() modifiziert werden. Der obere Codeausschnitt zeigt die Methode vor der Modifikation und der untere sie nach der Modifikation.
public static function updateAnnouncement($id) {
   $announce = AnnouncementDB::getAnnouncementByID($id);
   $announce->setTitle($_POST['title']);
   $announce->setText($_POST['text']);
   $announce->update();
   self::showSuccessMessages("updated");
   self::editAnnouncement($id);
}
public static function updateAnnouncement($id) {
   $datum = mktime($_POST['date_hour'], $_POST['date_minute'], 0, $_POST['date_month'], $_POST['date_day'], $_POST['date_year']);
   $datum_delete = mktime($_POST['date_delete_hour'], $_POST['date_delete_minute'], 0, $_POST['date_delete_month'], $_POST['date_delete_day'], $_POST['date_delete_year']);
       
   $failure = edit::datecheck($datum, $datum_delete);
       
   if (!$failure && (trim($_POST['title']) == "" || trim($_POST['text']) == "")) $failure = "Bitte alle Felder ausfüllen";
   // Wenn kein Fehler vorliegt
   if (!$failure) {
      $announce = AnnouncementDB::getAnnouncementByID($id);
      $announce->setTitle($_POST['title']);
      $announce->setText($_POST['text']);
      $announce->setDateDisplayFrom($datum);
      $announce->setDateDelete($datum_delete);
      $announce->update();
      self::showSuccessMessages("updated");
      self::editAnnouncement($id);
   } else {
      self::showSuccessMessages("dateFailure");
      self::editAnnouncement($id);
   }
}
  • Des Weiteren werden im Bearbeitungsmodus eines Aushangs die Datums- und Zeitangaben der Kalender nicht mehr auf den Zeitpunkt des Aufrufs gestellt, sondern auf die entsprechenden Zeitpunkte, wie sie in der Datenbank hinterlegt wurden. Die dafür notwenidgen Änderungen fanden in der Methode show() und loadTemplateVariable() statt.
  • In der Methode loadTemplateVariable() wurden die aus der Datenbank gelesenen Start- und Endzeipunkte eines Aushangs, in das von der Methode zurückgebende Array übergeben. Weil die Zeitpunkte nur bei Aushängen, die in der Datenbank gespeichert sind zu Verfügung stehen, wurde lediglich der für den Bearbeitungsmodus notwendige else-Zweig modifiziert. Der obere Codeausschnitt aus der Methode loadTemplateVariable() zeigt wieder die original Methode der untere die modifizierte.
    ...
    } else {
       $edit_data['windowtitle'] = "Mitteilung bearbeiten";
       $edit_data['okButton'] = "Übernehmen";
       $edit_data['action'] = "modify.php?id=".$announce->getId();
       $edit_data['mailannounce'] = "style=\"display: none\"";
       $edit_data['title'] = $announce->getTitle();
       $edit_data['text'] = $announce->getText();
    }
    return $edit_data;
 }
    ...
    } else {
       $edit_data['windowtitle'] = "Mitteilung bearbeiten";
       $edit_data['okButton'] = "Übernehmen";
       $edit_data['action'] = "modify.php?id=".$announce->getId();
       $edit_data['mailannounce'] = "style=\"display: none\"";
       $edit_data['title'] = $announce->getTitle();
       $edit_data['text'] = $announce->getText();
       $edit_data['selectdate'] = $announce->getDateDisplayFrom();
       $edit_data['selectdeldate'] = $announce->getDateDelete();
    }
    return $edit_data;
 }
  • In der Methode show() wurden daraufhin die Kalender mit den ausgelesen Zeitpunkten voreingestellt. Oberer Codeauschnitt zeigt wieder das Original, unterer die Modifikation.
 public static function show($edit_data = null) {
    if(self::$value==1){
       $layout="editannouncement_e2go.html";
    } else{
       $layout = self::getLayout();
    }
    $edit = new Template( PATH_TO_ROOT . "/announcement/templates/".$layout."" );
    self::$value=0;
    $calendar = self::initCalendar("show");
    $calendardel = self::initCalendar("del");
    if ($edit_data == null) $edit_data = self::loadTemplateVariable();
    $edit_data['selectdate'] = $calendar->printCalender("date", "dateExt");
    $edit_data['selecttime'] = $calendar->printCalender("date", "time");
    $edit_data['selectdeldate'] = $calendardel->printCalender("date_delete", "dateExt");
    $edit_data['selectdeltime'] = $calendardel->printCalender("date_delete", "time");

    if (isset($_SESSION["roleID"])) {
       $role = new Role($_SESSION["roleID"]);
       $edit_data['rolename'] = $role->getName($_SESSION["gender"], false);
       $edit_data['roleplay'] = "";
    } else {
       $edit_data['rolename'] = "none";
       $edit_data['roleplay'] = "style='display: none'";
    }
    eval( $edit->GetTemplate() );
 }
   public static function show($edit_data = null) {

      if(self::$value==1){
         $layout="editannouncement_e2go.html";
      } else{
         $layout = self::getLayout();
      }
      $edit = new Template( PATH_TO_ROOT . "/announcement/templates/".$layout."" );
      self::$value=0;
      $calendar = self::initCalendar("show");
      $calendardel = self::initCalendar("del");
      if ($edit_data != null) {
         $begin = getDate($edit_data['selectdate']);
    	 $calendar->setDate($begin['mday'], $begin['mon'], $begin['year']);
    	 $end = getDate($edit_data['selectdeldate']);
    	 $calendardel->setDate($end['mday'], $end['mon'], $end['year']);
    	 
    	 $beginTime = getDate($edit_data['selectdate']);
    	 $calendar->setTime($beginTime['hours'], $beginTime['minutes']);
    	 $endTime = getDate($edit_data['selectdeldate']);
         $calendardel->setTime($endTime['hours'], $endTime['minutes']);
      } else $edit_data = self::loadTemplateVariable();
      
      $edit_data['selectdate'] = $calendar->printCalender("date", "dateExt");
      $edit_data['selecttime'] = $calendar->printCalender("date", "time");
      $edit_data['selectdeldate'] = $calendardel->printCalender("date_delete", "dateExt");
      $edit_data['selectdeltime'] = $calendardel->printCalender("date_delete", "time");
 
      if (isset($_SESSION["roleID"])) {
         $role = new Role($_SESSION["roleID"]);
         $edit_data['rolename'] = $role->getName($_SESSION["gender"], false);
         $edit_data['roleplay'] = "";
      } else {
         $edit_data['rolename'] = "none";
         $edit_data['roleplay'] = "style='display: none'";
      }
      eval( $edit->GetTemplate() );
   }

Migration

Erstellte und veränderte Daten:

Allgemein

Klassen

Icons

Templates

Tests

Records

Menü-Änderungen

DB-Änderungen