diff --git a/.gitignore b/.gitignore
index 94a9fe1..92ee472 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@ resources/bootstrap/
resources/jquery/
xodx.log
config.ini
+/xodx.geany
+/gitcommit.bat
diff --git a/README.md b/README.md
index 0094164..63c4ee9 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,15 @@ In order to get the JavaScript dependencies [twitter bootstrap](http://twitter.g
in the xodx root directory (should be the same directory where you found this file).
+Configuration
+-------------
+### Profile Editor
+Here is described the configuration of the Profile Editor
+
+There are single and multiple Properties. They are stored in editor.single and editor.multiple respectivly. For every configured Property a corresponding RegEx has to be given. The RegEx can be given any Name, they are afterwards configured via regex.NameOfTheRegEx. Property and Regex are seperated by a comma, without space after the comma. editor.single and editor.multiple are composed with as many Property-RegEx Literals as needed, also seperated with a comma, without spaces.
+Example:
+
+
Code Conventions
----------------
Currently, this project is developed using [OntoWiki's coding standard](https://github.com/AKSW/OntoWiki/wiki/Coding-Standards).
diff --git a/classes/Xodx/ApplicationController.php b/classes/Xodx/ApplicationController.php
index 3d7d224..a225923 100644
--- a/classes/Xodx/ApplicationController.php
+++ b/classes/Xodx/ApplicationController.php
@@ -24,7 +24,7 @@
class Xodx_ApplicationController extends Saft_Controller
{
/**
- * This is the username of the currently logedin user
+ * This is the username of the currently loggedin user
*/
private $_user;
@@ -290,3 +290,4 @@ public function getUser ()
return $this->_user;
}
}
+
diff --git a/classes/Xodx/ConferenceController.php b/classes/Xodx/ConferenceController.php
new file mode 100644
index 0000000..9031abf
--- /dev/null
+++ b/classes/Xodx/ConferenceController.php
@@ -0,0 +1,77 @@
+addContent('templates/test.phtml');
+ return $template;
+ }
+
+ public function PrefixesAction()
+ {
+ $configHelper = new Xodx_ConfigHelper($this->_app);
+ var_dump($configHelper -> loadPropertiesSingle("conference"));
+ }
+
+ public function listAction($template)
+ {
+ $model = $this->_app->getBootstrap()->getResource('Model');
+ $configHelper = new Xodx_ConfigHelper($this->_app);
+ $typeUri = $configHelper -> getEditorClass("conference");
+
+ //Get all Conferences
+ $profiles = $model->sparqlQuery('SELECT DISTINCT ?event ?p ?o WHERE { ?event a <'. $typeUri .'> . ?event ?p ?o}');
+
+ //Reduce to Label
+ foreach ($profiles as $key => $array)
+ {
+ if (strcmp($array["p"],"http://www.w3.org/2000/01/rdf-schema#label") !=0)
+ {
+ unset($profiles[$key]);
+ }
+ }
+
+ //Add Values to $template
+ $template->profiles = $profiles;
+ $template->addContent('templates/list.phtml');
+ return $template;
+ }
+
+ public function newAction($template)
+ {
+ $bootstrap = $this->_app->getBootstrap();
+ $model = $bootstrap->getResource('model');
+ $uid = uniqid();
+ $conferenceId = $this->_app->getBaseUri() . '?c=conference&id=' . $uid;
+
+ //Create Conference
+ $valueToWrite = 'http://symbolicdata.org/Data/Model#Conference';
+ $valueArray = array('type' => 'uri', 'value' => $valueToWrite);
+ $keyToWrite = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
+
+ //echo ("
Writing: $conferenceId, $keyToWrite, $valueToWrite");
+ $model->addStatement($conferenceId, $keyToWrite, $valueArray);
+
+ //Add Temporary Title
+ $valueToWrite2 = $uid;
+ $valueArray2 = array('type' => 'literal', 'value' => $valueToWrite2);
+ $keyToWrite2 = 'http://www.w3.org/2000/01/rdf-schema#label';
+
+ //echo ("
Writing: $conferenceId, $keyToWrite2, $valueToWrite2");
+ $model->addStatement($conferenceId, $keyToWrite2, $valueArray2);
+
+ $template -> id = $conferenceId;
+ $template->addContent('templates/new.phtml');
+ return $template;
+ }
+}
diff --git a/classes/Xodx/ConfigHelper.php b/classes/Xodx/ConfigHelper.php
new file mode 100644
index 0000000..6e6a95e
--- /dev/null
+++ b/classes/Xodx/ConfigHelper.php
@@ -0,0 +1,143 @@
+loadProperties($editorType);
+ $single = array();
+
+ //Find those whose cardinality is single
+ foreach($properties as $key => $element)
+ {
+ if ($element['cardinality'] == 'single')
+ {
+ $single[$key] = $element;
+ }
+ }
+ return $single;
+ }
+
+ /**
+ * Returns all Multiple Properties for a given $editorType
+ */
+ public function loadPropertiesMultiple($editorType)
+ {
+ //Load All Properties
+ $properties = $this->loadProperties($editorType);
+ $multiple = array();
+
+ //Find those whose cardinality is multiple
+ foreach($properties as $key => $element)
+ {
+ if ($element['cardinality'] == 'multiple')
+ {
+ $multiple[$key] = $element;
+ }
+ }
+ return $multiple;
+ }
+
+ /**
+ * Returns all Properties with the corresponding RegExes, ready for preg_match
+ */
+ public function loadPropertyRegex()
+ {
+ $properties = array();
+ $config = $this->_app->getBootstrap()->getResource('Config');
+ $bothConfigs = explode(",",$config["editor.single"].",".$config["editor.multiple"]);
+ $skip = false;
+
+ foreach($bothConfigs as $key => $element)
+ {
+ if (!$skip)
+ {
+ $property = $element;
+ $skip = true;
+ }
+ else
+ {
+ $skip = false;
+ $properties[$property] = $this->propertyRegex($element);
+ }
+ }
+ return $properties;
+ }
+
+ /**
+ * Returns the RegEx for a given $regexName
+ */
+ public function propertyRegex($regexName)
+ {
+ //echo "propertyRegex: $regexName
";
+ $config = $this->_app->getBootstrap()->getResource('Config');
+ $regexString = 'regex.'
+ . $regexName;
+ return $config[$regexString];
+ }
+
+ /**
+ * Returns all properties for a given $editorType
+ */
+ public function loadProperties($editorType)
+ {
+ $propertiesPrepared = array();
+ $config = $this->_app->getBootstrap()->getResource('Config');
+ foreach ($config as $key => $value)
+ {
+ $keySplit = explode('.',$key);
+ if ($keySplit[0] == 'editor')
+ {
+ if ($keySplit[1] == $editorType)
+ {
+ if ($keySplit[2] == 'property')
+ {
+ $propertiesPrepared[$keySplit[3]][$keySplit[4]] = $value;
+ }
+ }
+ }
+ }
+ $properties = array();
+ foreach ($propertiesPrepared as $key => $value)
+ {
+ $properties[$value['uri']]['type'] = $value['type'];
+ $properties[$value['uri']]['cardinality'] = $value['cardinality'];
+ $properties[$value['uri']]['regex'] = $this->propertyRegex($value['type']);
+ }
+ return $properties;
+ }
+
+ /**
+ * Returns the class (e.g foaf:person)of an Editor with the given class as it is in the config
+ */
+ public function getEditorClass($editorType)
+ {
+ $config = $this->_app->getBootstrap()->getResource('Config');
+ foreach ($config as $key => $value)
+ {
+ $keySplit = explode('.', $key);
+ if ($keySplit[0] == 'editor')
+ {
+ if ($keySplit[1] == $editorType)
+ {
+ if ($keySplit[2] == 'class')
+ {
+ return $value;
+ }
+ }
+ }
+ }
+
+ }
+}
+
+
diff --git a/classes/Xodx/EditorController.php b/classes/Xodx/EditorController.php
new file mode 100644
index 0000000..b0195b3
--- /dev/null
+++ b/classes/Xodx/EditorController.php
@@ -0,0 +1,303 @@
+_app->getBootstrap();
+ $model = $bootstrap->getResource('model');
+ $configHelper = new Xodx_ConfigHelper($this->_app);
+ $rightsHelper = new Xodx_RightsHelper($this->_app);
+ $request = $bootstrap->getResource('request');
+ $classId = $request->getValue('class', 'get');
+ $typeUri = $configHelper->getEditorClass($classId);
+ $applicationController = $this->_app->getController('Xodx_ApplicationController');
+
+ //Needed switch to get personUri without passing it via $_GET
+ if (strcmp($classId, "person") == 0) {
+ $objectUri = urldecode($request->getValue('id', 'get'));
+ //Use current UserId if no personUri was passed
+ if (empty($objectUri)) {
+ $userId = $applicationController->getUser();
+ $userUri = $this->_app->getBaseUri() . '?c=person&id=' . $userId;
+ $objectUri = $userUri;
+ }
+ } else {
+ $objectUri = urldecode($request->getValue('id', 'get'));
+ }
+
+ //RightsManagement. Ask rightsHelper if action is allowed.
+ $hasRights = $rightsHelper->HasRights('edit', $classId, $objectUri);
+ if (!$hasRights) {
+ echo ('You do not have the rights for this. Sorry.');
+ return;
+ }
+
+ //Get Prefixes to be shown in the Editor
+ $allowedSinglePrefixes = $configHelper->loadPropertiesSingle($classId);
+ $allowedMultiplePrefixes = $configHelper->loadPropertiesMultiple($classId);
+
+ $template->caption = $classId;
+ $template->id = $objectUri;
+
+ //Switch if this was called from a Form.
+ if (count ($_POST) == 0) {
+ //Get Values from Database
+ $query = "SELECT ?p ?o WHERE { <"
+ . $objectUri
+ . "> a <"
+ . $typeUri
+ . "> . <"
+ . $objectUri
+ . "> ?p ?o }";
+
+ $profiles = $model->sparqlQuery($query);
+
+ //Add Values to $template
+ $template->allowedSinglePrefixes = $allowedSinglePrefixes;
+ $template->allowedMultiplePrefixes = $allowedMultiplePrefixes;
+ $template->profile = $profiles;
+ $template->addContent('templates/edit.phtml');
+
+ return $template;
+ } else {
+ //Process POSTed values and show ProfileEditor with
+ // a) Data from POST if it needs to be corrected
+ // b) Data from Database if everything was fine so the new data in the DB can be viewed.
+
+ $applicationController = $this->_app->getController('Xodx_ApplicationController');
+ $userId = $applicationController->getUser();
+ $userUri = $this->_app->getBaseUri() . '?c=person&id=' . $userId;
+ $stringArray = explode("id=", $userUri);
+ $name = $stringArray[1];
+
+ $prefixesSinglePrepare = array();
+ $valuesSinglePrepare = array();
+ $prefixesMultiplePrepare = array();
+ $valuesMultiplePrepare = array();
+ $valuesSingleNew = array();
+ $valuesMultipleNew = array();
+ $newKey;
+ $newValue;
+ $oldValue;
+ $changedAdd = array();
+ $changedDelete = array();
+ $wrong = array();
+
+ $query = 'SELECT ?p ?o WHERE { <'
+ . $objectUri
+ . '> a <'
+ . $typeUri
+ . '> . <'
+ . $objectUri
+ . '> ?p ?o }';
+ $databaseValues = $model->sparqlQuery($query);
+ $notFoundMultipleKeys = $databaseValues;
+
+ //prepare $_POST into prefix --> value
+ foreach ($_POST as $key => $value) {
+ $keyArray = explode('_', $key);
+ $number = (int)$keyArray[0];
+
+ //single Properties
+ if ($keyArray[1] == 'value') {
+ $valuesSinglePrepare[$number] = $value;
+ }
+
+ if ($keyArray[1] == 'prefix') {
+ $prefixesSinglePrepare[$number] = $value;
+ }
+
+ //multiple Properties
+ //$numberInKey is only needed if Property is multiple,
+ //so it is put inside the if statements.
+ if ($keyArray[1] == 'Mvalue') {
+ $numberInKey = (int)$keyArray[count($keyArray)-1];
+ $valuesMultiplePrepare[$number][$numberInKey] = $value;
+ }
+
+ if ($keyArray[1] == 'Mprefix') {
+ $numberInKey = (int)$keyArray[count($keyArray)-1];
+ $prefixesMultiplePrepare[$number] = $value;
+ }
+ }
+
+ foreach ($prefixesSinglePrepare as $key => $value) {
+ $valuesSingleNew[$value] = $valuesSinglePrepare[(int)$key];
+ }
+
+ //Single Properties
+ foreach ($valuesSingleNew as $key => $value) {
+ //Reset old values
+ $oldValue = "";
+ $newKey = $key;
+
+ //find corresponding value in query
+ //Searches for equivalent of $newKey
+ foreach ($databaseValues as $dbkey => $element) {
+ $p = $element['p'];
+ $o = $element['o'];
+ if (strcmp ($element['p'], $newKey) == 0) {
+ $oldValue = $element['o'];
+ unset($notFoundMultipleKeys[$dbkey]);
+ }
+ }
+
+ if ($value != $oldValue) {
+ $rString = $allowedSinglePrefixes[$key]['regex'];
+ //check Regex
+ if (preg_match($rString, $value)) {
+ //If Value matches, add it to the Values that are written and deleted from the DB.
+ $temp = array();
+ $temp['p'] = $newKey;
+ $temp['o'] = $value;
+ $changedAdd[] = $temp;
+ $temp = array();
+ $temp['p'] = $newKey;
+ $temp['o'] = $oldValue;
+ $changedDelete[] = $temp;
+ } else {
+ //If the Value is empty, it might not pass the Regex, but shall not be shown as wrong.
+ if (!empty ($value)) {
+ //Add Value to array which will later be shown as wrong values.
+ $wrong[$key] = $value;
+ }
+ }
+ }
+ }
+
+ //Multiple Properties
+ foreach ($prefixesMultiplePrepare as $prefixKey => $prefix) {
+ $values = $valuesMultiplePrepare[$prefixKey];
+ foreach ($values as $valueKey => $value) {
+ // 1. Forall key->value in newValues
+ // 1.1 Find corresponding value.
+ if ($value == '') {
+ break;
+ }
+ $found = false;
+
+ foreach ($databaseValues as $key => $element) {
+ //only for needed MultipleStatements
+ $p = $element['p'];
+ $o = $element['o'];
+ if (in_array($p, array_keys($allowedMultiplePrefixes))) {
+ if (strcmp ($p, $prefix) == 0) {
+ if (strcmp ($o, $value) == 0) {
+ //1.2 Delete this pair from $oldValues
+ //These Values are deleted from an extra Array
+ //At the End, all Values from this Array are deleted.
+ $found = true;
+ unset($notFoundMultipleKeys[$key]);
+ }
+ }
+ }
+ }
+ if (!$found) {
+ $rString = $allowedMultiplePrefixes[$prefix]['regex'];
+ //check Regex
+ if (preg_match($rString, $value)) {
+ //If Value matches, add it to the Values that are written to the DB.
+ $temp = array();
+ $temp['p'] = $prefix;
+ $temp['o'] = $value;
+ $changedAdd[] = $temp;
+ } else {
+ //If the Value is empty, it might not pass the Regex, but shall not be shown as wrong.
+ if (!empty ($value)) {
+ //Add Value to array which will later be shown as wrong values.
+ $temp = array();
+ $temp['p'] = $prefix;
+ $temp['o'] = $value;
+ $wrong[] = $temp;
+ }
+ }
+ }
+ }
+ }
+
+ //Check if there are any wrong Properties.
+ if (count($wrong) > 0 && !is_null($wrong)) {
+ //Allow wrong Properties to be corrected
+ //Therefore, change all the wrong values in the Values gotten from the database.
+ foreach ($wrong as $key => $value) {
+ $databaseValues[] = $value;
+ }
+ //Add Values to $template
+ $template->allowedSinglePrefixes = $allowedSinglePrefixes;
+ $template->allowedMultiplePrefixes = $allowedMultiplePrefixes;
+ $template->profile = $databaseValues;
+ $template->config = $config;
+ $template->wrong = $wrong;
+ $template->addContent('templates/edit.phtml');
+ return $template;
+ } else {
+ //Prepare multiple Keys (deleted)
+ foreach ($notFoundMultipleKeys as $key => $element) {
+ $p = $element['p'];
+ $o = $element['o'];
+
+ if (in_array($p, array_keys($allowedMultiplePrefixes))) {
+ $temp = array();
+ $temp['p'] = $p;
+ $temp['o'] = $o;
+ $changedDelete[] = $temp;
+ }
+ }
+ //Write Properties to Database
+ foreach ($changedDelete as $key => $value) {
+ $valueArray = array(
+ 'type' => 'literal',
+ 'value' => $value['o']);
+ $keyToDelete = $value['p'];
+ $valueToDelete = $value['o'];
+ $model->deleteStatement($objectUri, $keyToDelete, $valueArray);
+ }
+ foreach ($changedAdd as $key => $value) {
+ $valueArray = array(
+ 'type' => 'literal',
+ 'value' => $value['o']);
+ $keyToWrite = $value['p'];
+ $valueToWrite = $value['o'];
+ $model->addStatement($objectUri, $keyToWrite, $valueArray);
+ }
+
+ //Show Editor with Values from Database.
+ $_POST = null;
+ $template = $this->editAction($template);
+ return $template;
+ }
+ }
+ }
+
+ //The following functions are only for debug purposes,
+ //but left here, in case anybody might need them.
+ public function loadPropertiesAction()
+ {
+ $configHelper = new Xodx_ConfigHelper($this->_app);
+ return $configHelper->loadProperties();
+ }
+
+ public function loadPropertiesSingleAction()
+ {
+ $configHelper = new Xodx_ConfigHelper($this->_app);
+ var_dump($configHelper->loadPropertiesSingle('conference'));
+ }
+
+ public function loadPropertiesMultipleAction()
+ {
+ $configHelper = new Xodx_ConfigHelper($this->_app);
+ var_dump($configHelper->loadPropertiesMultiple('person'));
+ }
+}
diff --git a/classes/Xodx/PersonController.php b/classes/Xodx/PersonController.php
index d35ed1a..0534ca0 100644
--- a/classes/Xodx/PersonController.php
+++ b/classes/Xodx/PersonController.php
@@ -195,7 +195,7 @@ public function getUserForPerson ($personUri)
}
/**
- * Add a new contact to the list of freinds of a person
+ * Add a new contact to the list of friends of a person
* This is a one-way connection, the contact doesn't has to approve it
*
* @param $personUri the URI of the person to whome the contact should be added
@@ -226,7 +226,7 @@ public function addFriend ($personUri, $contactUri)
$activityController->addActivity($personUri, $nsAair . 'MakeFriend', $object);
// Send Ping to new friend
- $pingbackController = $this->_app->getController('Xodx_PingbackController');
+ $pingbackController = $this->_app->getController('Xodx_PingbackController');
$pingbackController->sendPing($personUri, $contactUri, 'Do you want to be my friend?');
// Subscribe to new friend
diff --git a/classes/Xodx/RightsHelper.php b/classes/Xodx/RightsHelper.php
new file mode 100644
index 0000000..38b4ad7
--- /dev/null
+++ b/classes/Xodx/RightsHelper.php
@@ -0,0 +1,43 @@
+_app->getBootstrap();
+ $model = $bootstrap->getResource('model');
+ $applicationController = $this->_app->getController('Xodx_ApplicationController');
+
+ //This obviously has to be expanded.
+ if (strcmp($type, 'person') == 0)
+ {
+ $userId = $applicationController->getUser();
+ $userUri = $this->_app->getBaseUri() . '?c=person&id=' . $userId;
+ if (strcmp($userUri, $id) == 0)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ if (strcmp($type, 'conference') == 0)
+ {
+ return true;
+ }
+ }
+}
+
diff --git a/classes/Xodx/UserController.php b/classes/Xodx/UserController.php
index 063d2df..d80cab1 100644
--- a/classes/Xodx/UserController.php
+++ b/classes/Xodx/UserController.php
@@ -13,6 +13,7 @@
* this includes:
* - subscribing to a feed
* - getting notifications
+ *
*/
class Xodx_UserController extends Xodx_ResourceController
{
diff --git a/config.ini-dist b/config.ini-dist
index 995cee8..ebb05d5 100644
--- a/config.ini-dist
+++ b/config.ini-dist
@@ -1,13 +1,94 @@
[xodx]
; In this section you can tune some parameters of XODX
-xodx.model = "http://localhost/Xodx"
+xodx.model = "http://192.168.56.101/xodx-dev"
push.enable = true;
push.hub = "http://pubsubhubbub.appspot.com"
;push.hub = "http://localhost:8123/"
;push.hub = "http://localhost/OntoWiki/pubsub/hubbub"
+;Parameters for Profile Editor
+
+editor.person.class = http://xmlns.com/foaf/0.1/Person
+
+editor.person.property.1.uri = http://xmlns.com/foaf/0.1/nick
+editor.person.property.1.type = Literal
+editor.person.property.1.cardinality = single
+editor.person.property.2.uri = http://xmlns.com/foaf/0.1/name
+editor.person.property.2.type = Literal
+editor.person.property.2.cardinality = single
+editor.person.property.3.uri = http://xmlns.com/foaf/0.1/depiction
+editor.person.property.3.type = URL
+editor.person.property.3.cardinality = single
+
+editor.person.property.4.uri = http://xmlns.com/foaf/0.1/schoolHomepage
+editor.person.property.4.type = URL
+editor.person.property.4.cardinality = multiple
+editor.person.property.5.uri = http://xmlns.com/foaf/0.1/jabberID
+editor.person.property.5.type = Email
+editor.person.property.5.cardinality = multiple
+editor.person.property.6.uri = http://www.w3.org/2000/01/rdf-schema#seeAlso
+editor.person.property.6.type = URL
+editor.person.property.6.cardinality = multiple
+editor.person.property.7.uri = http://xmlns.com/foaf/0.1/based_near
+editor.person.property.7.type = URL
+editor.person.property.7.cardinality = multiple
+editor.person.property.8.uri = http://xmlns.com/foaf/0.1/weblog
+editor.person.property.8.type = URL
+editor.person.property.8.cardinality = multiple
+editor.person.property.9.uri = http://xmlns.com/foaf/0.1/workplaceHomepage
+editor.person.property.9.type = URL
+editor.person.property.9.cardinality = multiple
+
+editor.conference.class = http://symbolicdata.org/Data/Model#Conference
+
+editor.conference.property.1.uri = http://www.w3.org/2000/01/rdf-schema#label
+editor.conference.property.1.type = String
+editor.conference.property.1.cardinality = single
+editor.conference.property.2.uri = http://www.w3.org/2002/12/cal/ical#status
+editor.conference.property.2.type = String
+editor.conference.property.2.cardinality = single
+editor.conference.property.3.uri = http://www.w3.org/2002/12/cal/ical#summary
+editor.conference.property.3.type = String
+editor.conference.property.3.cardinality = single
+editor.conference.property.4.uri = http://www.w3.org/2002/12/cal/ical#description
+editor.conference.property.4.type = Literal
+editor.conference.property.4.cardinality = single
+editor.conference.property.5.uri = http://www.w3.org/2002/12/cal/ical#location
+editor.conference.property.5.type = String
+editor.conference.property.5.cardinality = single
+
+editor.conference.property.6.uri = http://www.w3.org/2002/12/cal/ical#contact
+editor.conference.property.6.type = String
+editor.conference.property.6.cardinality = single
+editor.conference.property.7.uri = http://www.w3.org/2002/12/cal/ical#url
+editor.conference.property.7.type = URL
+editor.conference.property.7.cardinality = single
+editor.conference.property.8.uri = http://www.w3.org/2002/12/cal/ical#dtstart
+editor.conference.property.8.type = DateTime
+editor.conference.property.8.cardinality = single
+editor.conference.property.9.uri = http://www.w3.org/2002/12/cal/ical#dtend
+editor.conference.property.9.type = DateTime
+editor.conference.property.9.cardinality = single
+editor.conference.property.10.uri = http://www.w3.org/2002/12/cal/ical#created
+editor.conference.property.10.type = DateTime
+editor.conference.property.10.cardinality = single
+
+editor.conference.property.11.uri = ical:lastModified
+editor.conference.property.11.type = DateTime
+editor.conference.property.11.cardinality = single
+
+; by http://mathiasbynens.be/demo/url-regex
+regex.URL = "_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})))(?::\d{2,5})?(?:/[^\s]*)?$_iuS"
+; by http://www.regular-expressions.info/email.html
+regex.Email = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
+regex.Literal = "#[\s\S]+#"
+regex.String = "#[\s\S]+#"
+; by http://regexlib.com/REDetails.aspx?regexp_id=610
+;regex.DateTime = " ^(?=\d)(?:(?:31(?!.(?:0?[2469]|11))|(?:30|29)(?!.0?2)|29(?=.0?2.(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))(?:\x20|$))|(?:2[0-8]|1\d|0?[1-9]))([-./])(?:1[012]|0?[1-9])\1(?:1[6-9]|[2-9]\d)?\d\d(?:(?=\x20\d)\x20|$))?(((0?[1-9]|1[012])(:[0-5]\d){0,2}(\x20[AP]M))|([01]\d|2[0-3])(:[0-5]\d){1,2})?$"
+regex.DateTime = "#(\d){1,4}-(\d){1,2}-(\d){1,2}#"
+
[erfurt]
; In this section you can configure the Erfurt framework
diff --git a/resources/img/xodx_20.png b/resources/img/xodx_20.png
index 8176d98..8371c6e 100644
Binary files a/resources/img/xodx_20.png and b/resources/img/xodx_20.png differ
diff --git a/templates/conference/list.phtml b/templates/conference/list.phtml
new file mode 100644
index 0000000..06e2b99
--- /dev/null
+++ b/templates/conference/list.phtml
@@ -0,0 +1,14 @@
+