From 51c910e51f60262dad88fc2b5e539a34ebd8e8b1 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Tue, 21 May 2019 17:17:29 +0200 Subject: [PATCH 01/41] Started working on abstract db models from scratch --- .buildpath | 1 + .settings/org.eclipse.php.ui.prefs | 2 + src/admin/clubs.php | 2 + src/admin/common/abstract/model.php | 37 +++++++ src/admin/common/abstract/modelfactory.php | 111 +++++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 .settings/org.eclipse.php.ui.prefs create mode 100644 src/admin/common/abstract/model.php create mode 100644 src/admin/common/abstract/modelfactory.php diff --git a/.buildpath b/.buildpath index e9e2cb4..a59af60 100644 --- a/.buildpath +++ b/.buildpath @@ -1,6 +1,7 @@ + diff --git a/.settings/org.eclipse.php.ui.prefs b/.settings/org.eclipse.php.ui.prefs new file mode 100644 index 0000000..9a3362f --- /dev/null +++ b/.settings/org.eclipse.php.ui.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.php.ui.text.custom_code_templates= diff --git a/src/admin/clubs.php b/src/admin/clubs.php index f52ed19..c82b2d7 100644 --- a/src/admin/clubs.php +++ b/src/admin/clubs.php @@ -8,6 +8,8 @@ defined('_JEXEC') or die; JLoader::discover('Clubs', JPATH_ROOT . '/administrator/components/com_clubs/mymodels'); JLoader::registerPrefix('AbstractClubs', JPATH_ROOT . '/administrator/components/com_clubs/abstract'); +JLoader::registerPrefix('AbstractCommonClubs', JPATH_ROOT . '/administrator/components/com_clubs/common/abstract'); +// JLoader::registerPrefix('ClubsHelper', JPATH_ROOT . '/administrator/components/com_clubs/common/helper'); $controller = BaseController::getInstance("Clubs"); $input = Factory::getApplication()->input; diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php new file mode 100644 index 0000000..3c7bfca --- /dev/null +++ b/src/admin/common/abstract/model.php @@ -0,0 +1,37 @@ +id; + } + + public function setId($id) + { + $this->id = $id; + } + + private $values = null; + + protected function getValues() + { + if(is_null($this->values)) + // FIXME fetch cache + return; + + return $this->values; + } + + public function setValues($values) + { + $this->values = $values; + } + +} diff --git a/src/admin/common/abstract/modelfactory.php b/src/admin/common/abstract/modelfactory.php new file mode 100644 index 0000000..8b8b8cb --- /dev/null +++ b/src/admin/common/abstract/modelfactory.php @@ -0,0 +1,111 @@ +array('col'=>'Name', type=>'string'), + * 'size'=>array('col'=>'area', type=>'int') + * ) + * + * The inner arrays contain the following entries: + * - col: The name of the column in the database. Mandatory. + * - type: The type of the column. If nothing is specified, string is assumed. + * - string + * - int + * - float + * - ref + * - ref: (only with type='ref') The name of the class that is referenced + */ + protected abstract function getAttributes(); + + private $attributes = null; + private function fetchAttributes() + { + if($this->attributes === null) + $this->attributes = $this->getAttributes(); + + return $this->attributes; + } + + protected abstract function getTableName(); + protected abstract function getClassName(); + + /** + * + * @param string $condition + * @return array + */ + protected function loadElements($condition = null) + { + $db = Factory::getDbo(); + $q = $db->getQuery(true); + +// $columns = array_map(function($arr) use ($q){ return $q->qn('main' . $arr['col']); }, $this->fetchAttributes()); + $columns = array(); + foreach($this->fetchAttributes() as $k=>$v) + { + $columns[] = $q->qn('main' . $v['col'], $k); + } + + $q->select('main.id')->select($columns); + $q->from($this->getTableName(), 'main'); + + // TODO Joins + + if($condition !== null) + $q->where($condition); + + $db->setQuery($q); + $db->execute(); + + $it = $db->getIterator(); + $ret = array(); + while($it->valid()) + { + $row = $it->current(); + $ret[] = $this->generateObject($row); + $it->next(); + } + + return $ret; + } + + /** + * + * @param int $id + */ + protected function loadById($id) { + $arr = $this->loadElements("main.id = " . ((int)$id) ); + if(sizeof($arr) == 0) + throw new ElementNotFoundException(); + + return $arr[0]; + } + + protected function generateObject($row) + { + $name = $this->getClassName(); + $obj = new $name(); + + $obj->setId($row['id']); + + unset($row['id']); + $obj->setValues($row); + + return $obj; + } +} From fd0a0f7f4d0a9424b0f671eeefbfbaa61a44a75b Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 22 May 2019 11:35:28 +0200 Subject: [PATCH 02/41] Basic model is mainly written, completely untested --- src/admin/common/abstract/model.php | 219 ++++++++++++++++++++- src/admin/common/abstract/modelfactory.php | 41 +++- 2 files changed, 249 insertions(+), 11 deletions(-) diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 3c7bfca..6046df4 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -1,12 +1,15 @@ values)) - // FIXME fetch cache - return; + $this->loadDataFromDatabase(); return $this->values; } + public function isNew() + { + return $this->new; + } + public function setValues($values) { $this->values = $values; } + public function markAsNew($new) + { + $this->new = $new; + } + + public function fillDefaultValues() + {} + + /** + * @return AbstractCommonClubsModelFactory + */ + protected abstract function getFactory(); + + public function save() + { + $factory = $this->getFactory(); + $attribs = $factory->getAttributes(); + + $db = Factory::getDbo(); + $q = $db->getQuery(true); + + $db->transactionStart(); + + if($this->new) + $this->prepareInsert($attribs, $factory, $q); + else + $this->prepareUpdate($attribs, $factory, $q); + + $db->setQuery($q); + $db->execute(); + + if($this->new) + { + $this->finishInsert($db); + } + + $db->transactionCommit(); + } + + private function loadDataFromDatabase() + { + $factory = $this->getFactory(); + $attribs = $factory->getAttributes(); + + $db = Factory::getDbo(); + $q = $db->getQuery(true); + + foreach($attribs as $k => $v) + { + $q->select($q->qn($v['col'], $k)); + } + $q->from($factory->getTableName()); + $q->where("id = {$this->id}"); + + $db->setQuery($q); + $db->execute(); + + $this->values = $db->loadAssoc(); + } + + /** + * + * @param array $rawData + * @param array $attribs + * @param JDatabaseQuery $q + * @return string[]|number[]|NULL[] + */ + private function quoteData($rawData, $attribs, $q) + { + $quotedData = array(); + + foreach($attribs as $k => $v) + { + if(empty($v['type'])) + $v['type'] = 'string'; + + if($rawData[$k] === NULL) + $quotedData[$k] = 'NULL'; + + switch($v['type']) + { + case 'string': + $quotedData[$k] = $q->q($rawData[$k]); + break; + + case 'int': + $quotedData[$k] = (int) $rawData[$k]; + break; + + case 'float': + $quotedData[$k] = (float) $rawData[$k]; + break; + + case 'ref': + if($v['ref'] === null) + $quotedData[$k] = 'NULL'; + else + $quotedData[$k] = $rawData[$k]->getId(); + break; + } + } + + return $quotedData; + } + + protected function filterRawData($values) + { + return $values; + } + + protected function filterQuotedData($quoted) + { + return $quoted; + } + + /** + * + * @param array $attribs + * @param JDatabaseQuery $q + * @return array + */ + private function getQuotedData($attribs, $q) + { + $rawData = $this->getValues(); + $rawData = $this->filterRawData($rawData); + + $quotedData = $this->quoteData($rawData, $attribs, $q); + $quotedData = $this->filterQuotedData($quotedData); + + return $quotedData; + } + + /** + * + * @param array $attribs + * @param AbstractCommonClubsModelFactory $factory + * @param JDatabaseQuery $q + */ + private function prepareInsert($attribs, $factory, $q) + { + $q->insert($factory->getTableName()); + + $dbcols = array_map(function ($v){return $v['col'];}, $attribs); + + $q->columns($q->qn($dbcols)); + + $quotedData = $this->getQuotedData($attribs, $q); + + $q->values(join(', ', $quotedData)); + } + + /** + * + * @param JDatabaseDriver $db + */ + private function finishInsert($db) + { + $this->id = $db->insertid(); + $this->new = false; + } + + /** + * + * @param array $attribs + * @param AbstractCommonClubsModelFactory $factory + * @param JDatabaseQuery $q + */ + private function prepareUpdate($attribs, $factory, $q) + { + $q->update($factory->getTableName()); + + $dbcols = array_map(function ($v){return $v['col'];}, $attribs); + + $quotedData = $this->getQuotedData($attribs, $q); + + $q->set(array_map(function($col, $data){ + return "$col = $data"; + }, $dbcols, $quotedData)); + + $q->where("id = {$this->id}"); + } + + public function delete() + { + $db = Factory::getDbo(); + $q = $db->getQuery(true); + + $factory = $this->getFactory(); + + $q->delete($factory->getTableName()); + $q->where("id = {$this->id}"); + + $db->transactionStart(); + $db->setQuery($q); + $db->execute(); + $db->transactionCommit(); + } + + public function pack() + { + // TODO + } + + public function unpack($str) + { + // TODO + } + } diff --git a/src/admin/common/abstract/modelfactory.php b/src/admin/common/abstract/modelfactory.php index 8b8b8cb..7eafc1b 100644 --- a/src/admin/common/abstract/modelfactory.php +++ b/src/admin/common/abstract/modelfactory.php @@ -8,7 +8,7 @@ defined('_JEXEC') or die; class ElementNotFoundException extends Exception {} -abstract class AbstractClubsModelFactory +abstract class AbstractCommonClubsModelFactory { /* @@ -30,7 +30,7 @@ abstract class AbstractClubsModelFactory * - ref * - ref: (only with type='ref') The name of the class that is referenced */ - protected abstract function getAttributes(); + public abstract function getAttributes(); private $attributes = null; private function fetchAttributes() @@ -38,18 +38,18 @@ abstract class AbstractClubsModelFactory if($this->attributes === null) $this->attributes = $this->getAttributes(); - return $this->attributes; + return $this->attributes; } - protected abstract function getTableName(); - protected abstract function getClassName(); + public abstract function getTableName(); + public abstract function getClassName(); /** * * @param string $condition * @return array */ - protected function loadElements($condition = null) + public function loadElements($condition = null) { $db = Factory::getDbo(); $q = $db->getQuery(true); @@ -88,7 +88,7 @@ abstract class AbstractClubsModelFactory * * @param int $id */ - protected function loadById($id) { + public function loadById($id) { $arr = $this->loadElements("main.id = " . ((int)$id) ); if(sizeof($arr) == 0) throw new ElementNotFoundException(); @@ -96,16 +96,39 @@ abstract class AbstractClubsModelFactory return $arr[0]; } - protected function generateObject($row) + private function generatePlainObject($id) { $name = $this->getClassName(); $obj = new $name(); - $obj->setId($row['id']); + $obj->setId($id); + + return $obj; + } + + protected function generateObject($row) + { + $obj = $this->generatePlainObject($row['id']); + $obj->marksAsNew(false); unset($row['id']); $obj->setValues($row); return $obj; } + + public function createNew() + { + $obj = $this->generatePlainObject('new'); + $obj->markAsNew(true); + + $attribs = array_map(function($v){ + return Null; + }, $this->fetchAttributes()); + $obj->setValues($attribs); + + $obj->fillDefaultValues(); + + return $obj; + } } From cb6068bfd6be833d16ae196795ef21b0b8d34f29 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 22 May 2019 14:03:18 +0200 Subject: [PATCH 03/41] Prepared work of models to be useful with basic attributes --- src/admin/common/abstract/model.php | 121 +++++++++++++++++++-- src/admin/common/abstract/modelfactory.php | 19 ++-- 2 files changed, 123 insertions(+), 17 deletions(-) diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 6046df4..e77a31d 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -8,8 +8,12 @@ defined('_JEXEC') or die; abstract class AbstractCommonClubsModel { - protected $id; // TODO private possible? - protected $new; + // TODO Adddata validator + // TODO Make setting of values attribute fail in case of problems + // FIXME Add Joins in select statements + + private $id; + private $new; public function getId() { @@ -98,7 +102,73 @@ abstract class AbstractCommonClubsModel $db->setQuery($q); $db->execute(); - $this->values = $db->loadAssoc(); + $values = $db->loadAssoc(); + + $values = $this->unpackExternalReferencesFromKeys($values); + + $this->values = $values; + } + + private function packExternalReferencesAsKeys($vals) + { + foreach($this->getFactory()->getAttributes() as $k => $v) + { + if($v['type'] !== 'ref') + continue; + + $vals[$k] = $vals[$k]->getId(); + } + + return $vals; + } + + private function unpackExternalReferencesFromKeys($vals) + { + foreach($this->getFactory()->getAttributes() as $k => $v) + { + if($v['type'] !== 'ref') + continue; + + if(empty($v['ref'])) + throw new Exception('External reference of unknown class found.'); + + $vals[$k] = $this->loadExternalReferenceAsObject($v['ref'], $vals[$k]); + } + + return $vals; + } + + private function loadExternalReferenceAsObject($className, $value) + { + if(! is_int($value)) + throw new Exception('Reference with non-integer value'); + + $factory = $this->getFactoryOfClass($className); + return $factory->loadById($value); + } + + private static const CLASSNAME_MAP = array( + + ); + + /** + * @todo This must be done better and more cleanly + * @param string $className + * @return AbstractCommonClubsModelFactory + */ + private function getFactoryOfClass($className) + { + if(empty(self::CLASSNAME_MAP[$className])) + { + $parts = array(); + if(preg_match('/^CommonClubsModel(.*)/', $className, $parts)) + { + return "CommonClubsModelFactory{$parts[1]}"; + } + throw new Exception("Unknown mapping of class $className"); + } + + return self::CLASSNAME_MAP[$className]; } /** @@ -146,12 +216,12 @@ abstract class AbstractCommonClubsModel return $quotedData; } - protected function filterRawData($values) + protected function filterDatabaseRawData($values) { return $values; } - protected function filterQuotedData($quoted) + protected function filterDatabaseQuotedData($quoted) { return $quoted; } @@ -165,10 +235,10 @@ abstract class AbstractCommonClubsModel private function getQuotedData($attribs, $q) { $rawData = $this->getValues(); - $rawData = $this->filterRawData($rawData); + $rawData = $this->filterDatabaseRawData($rawData); $quotedData = $this->quoteData($rawData, $attribs, $q); - $quotedData = $this->filterQuotedData($quotedData); + $quotedData = $this->filterDatabaseQuotedData($quotedData); return $quotedData; } @@ -239,14 +309,47 @@ abstract class AbstractCommonClubsModel $db->transactionCommit(); } + /** + * + * @param AbstractCommonClubsModelFactory $factory + * @param string $colName + */ + protected function fetchAssociatedElements($factory, $colName) + { + $condition = "main.$colName = {$this->id}"; + return $factory->loadElements($condition); + } + + protected function filterPackData($values) + { + return $values; + } + + protected function filterUnpackData($values) + { + return $values; + } + public function pack() { - // TODO + $vals = $this->getValues(); + + $vals = $this->packExternalReferencesAsKeys($vals); + $vals = $this->filterPackData($vals); + + $json = json_encode($vals); + return urldecode($json); } public function unpack($str) { - // TODO + $json = urlencode($str); + $data = json_decode($json, true); + + $vals = $this->unpackExternalReferencesFromKeys($data); + $vals = $this->filterUnpackData($vals); + + $this->setValues($vals); } } diff --git a/src/admin/common/abstract/modelfactory.php b/src/admin/common/abstract/modelfactory.php index 7eafc1b..ebb3169 100644 --- a/src/admin/common/abstract/modelfactory.php +++ b/src/admin/common/abstract/modelfactory.php @@ -55,13 +55,13 @@ abstract class AbstractCommonClubsModelFactory $q = $db->getQuery(true); // $columns = array_map(function($arr) use ($q){ return $q->qn('main' . $arr['col']); }, $this->fetchAttributes()); - $columns = array(); - foreach($this->fetchAttributes() as $k=>$v) - { - $columns[] = $q->qn('main' . $v['col'], $k); - } +// $columns = array(); +// foreach($this->fetchAttributes() as $k=>$v) +// { +// $columns[] = $q->qn('main' . $v['col'], $k); +// } - $q->select('main.id')->select($columns); + $q->select('main.id AS id');//->select($columns); $q->from($this->getTableName(), 'main'); // TODO Joins @@ -111,8 +111,11 @@ abstract class AbstractCommonClubsModelFactory $obj = $this->generatePlainObject($row['id']); $obj->marksAsNew(false); - unset($row['id']); - $obj->setValues($row); + //unset($row['id']); + //$obj->setValues($row); + + // Do not trigger cache if no needed + //$obj->getValues(); return $obj; } From b3e28d788420ca64a032fce39fdff18430e2eea0 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 22 May 2019 14:22:45 +0200 Subject: [PATCH 04/41] Removed minor bug and implemented stub of first class --- .buildpath | 6 +++- src/admin/clubs.php | 1 + src/admin/common/abstract/model.php | 13 +++++++-- src/admin/common/abstract/modelfactory.php | 2 +- src/admin/common/models/club.php | 14 +++++++++ src/admin/common/models/factory/club.php | 34 ++++++++++++++++++++++ 6 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 src/admin/common/models/club.php create mode 100644 src/admin/common/models/factory/club.php diff --git a/.buildpath b/.buildpath index a59af60..635eec4 100644 --- a/.buildpath +++ b/.buildpath @@ -1,7 +1,11 @@ - + + + + + diff --git a/src/admin/clubs.php b/src/admin/clubs.php index c82b2d7..f1320ba 100644 --- a/src/admin/clubs.php +++ b/src/admin/clubs.php @@ -10,6 +10,7 @@ JLoader::discover('Clubs', JPATH_ROOT . '/administrator/components/com_clubs/mym JLoader::registerPrefix('AbstractClubs', JPATH_ROOT . '/administrator/components/com_clubs/abstract'); JLoader::registerPrefix('AbstractCommonClubs', JPATH_ROOT . '/administrator/components/com_clubs/common/abstract'); // JLoader::registerPrefix('ClubsHelper', JPATH_ROOT . '/administrator/components/com_clubs/common/helper'); +JLoader::registerPrefix('CommonClubsModel', JPATH_ROOT . '/administrator/components/com_clubs/common/models'); $controller = BaseController::getInstance("Clubs"); $input = Factory::getApplication()->input; diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index e77a31d..b87f6fa 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -94,7 +94,8 @@ abstract class AbstractCommonClubsModel foreach($attribs as $k => $v) { - $q->select($q->qn($v['col'], $k)); + $rawColName = isset($v['col']) ? $v['col'] : $k; + $q->select($q->qn($rawColName, $k)); } $q->from($factory->getTableName()); $q->where("id = {$this->id}"); @@ -253,7 +254,11 @@ abstract class AbstractCommonClubsModel { $q->insert($factory->getTableName()); - $dbcols = array_map(function ($v){return $v['col'];}, $attribs); + $dbcols = array(); + foreach($attribs as $k => $v) + { + $dbcols[] = isset($v['col']) ? $v['col'] : $k; + } $q->columns($q->qn($dbcols)); @@ -282,7 +287,9 @@ abstract class AbstractCommonClubsModel { $q->update($factory->getTableName()); - $dbcols = array_map(function ($v){return $v['col'];}, $attribs); + $dbcols = array(); + foreach($attribs as $k => $v) + $dbcols[] = isset($v['col']) ? $v['col'] : $k; $quotedData = $this->getQuotedData($attribs, $q); diff --git a/src/admin/common/abstract/modelfactory.php b/src/admin/common/abstract/modelfactory.php index ebb3169..ac3ce15 100644 --- a/src/admin/common/abstract/modelfactory.php +++ b/src/admin/common/abstract/modelfactory.php @@ -22,7 +22,7 @@ abstract class AbstractCommonClubsModelFactory * ) * * The inner arrays contain the following entries: - * - col: The name of the column in the database. Mandatory. + * - col: The name of the column in the database. Defaults to the key name * - type: The type of the column. If nothing is specified, string is assumed. * - string * - int diff --git a/src/admin/common/models/club.php b/src/admin/common/models/club.php new file mode 100644 index 0000000..12ed40a --- /dev/null +++ b/src/admin/common/models/club.php @@ -0,0 +1,14 @@ +array(), + 'address'=>array(), + 'city'=>array(), + 'homepage'=>array(), + 'mail'=>array(), + 'iban'=>array(), + 'bic'=>array(), + 'charitable'=>array('type'=>'int'), + 'president'=>array('type'=>'int', 'ref'=>'CommonClubsModelUser') + ); + } + + public function getTableName() + { + return '#__club_clubs'; + } + + public function getClassName() + { + return 'CommonClubsModelClub'; + } + + +} \ No newline at end of file From b868f0fe8681bce57712a160a3eb78a4b68de2da Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 22 May 2019 15:26:24 +0200 Subject: [PATCH 05/41] Removed some bugs in abstract class, added testcase --- src/admin/common/abstract/model.php | 4 +- .../{modelfactory.php => model/factory.php} | 10 ++-- src/admin/common/models/club.php | 46 +++++++++++++++++++ src/admin/views/test/tmpl/default.php | 16 +++++++ src/admin/views/test/view.html.php | 35 ++++++++++++++ 5 files changed, 103 insertions(+), 8 deletions(-) rename src/admin/common/abstract/{modelfactory.php => model/factory.php} (94%) create mode 100644 src/admin/views/test/tmpl/default.php create mode 100644 src/admin/views/test/view.html.php diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index b87f6fa..e3c4ce8 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -127,7 +127,7 @@ abstract class AbstractCommonClubsModel { foreach($this->getFactory()->getAttributes() as $k => $v) { - if($v['type'] !== 'ref') + if(empty($v['type']) || $v['type'] !== 'ref') continue; if(empty($v['ref'])) @@ -148,7 +148,7 @@ abstract class AbstractCommonClubsModel return $factory->loadById($value); } - private static const CLASSNAME_MAP = array( + private const CLASSNAME_MAP = array( ); diff --git a/src/admin/common/abstract/modelfactory.php b/src/admin/common/abstract/model/factory.php similarity index 94% rename from src/admin/common/abstract/modelfactory.php rename to src/admin/common/abstract/model/factory.php index ac3ce15..d27f5c8 100644 --- a/src/admin/common/abstract/modelfactory.php +++ b/src/admin/common/abstract/model/factory.php @@ -62,7 +62,7 @@ abstract class AbstractCommonClubsModelFactory // } $q->select('main.id AS id');//->select($columns); - $q->from($this->getTableName(), 'main'); + $q->from($this->getTableName() . ' AS main'); // TODO Joins @@ -72,13 +72,11 @@ abstract class AbstractCommonClubsModelFactory $db->setQuery($q); $db->execute(); - $it = $db->getIterator(); + $rows = $db->loadAssocList(); $ret = array(); - while($it->valid()) + foreach($rows as $row) { - $row = $it->current(); $ret[] = $this->generateObject($row); - $it->next(); } return $ret; @@ -109,7 +107,7 @@ abstract class AbstractCommonClubsModelFactory protected function generateObject($row) { $obj = $this->generatePlainObject($row['id']); - $obj->marksAsNew(false); + $obj->markAsNew(false); //unset($row['id']); //$obj->setValues($row); diff --git a/src/admin/common/models/club.php b/src/admin/common/models/club.php index 12ed40a..024ebdb 100644 --- a/src/admin/common/models/club.php +++ b/src/admin/common/models/club.php @@ -11,4 +11,50 @@ class CommonClubsModelClub extends AbstractCommonClubsModel return new CommonClubsModelFactoryClub(); } + public function getName() + { + return $this->getValues()['name']; + } + + public function getAddress() + { + return $this->getValues()['address']; + } + + public function getCity() + { + return $this->getValues()['city']; + } + + public function getHomepage() + { + return $this->getValues()['homepage']; + } + + public function getMail() + { + return $this->getValues()['mail']; + } + + public function getIban() + { + return $this->getValues()['iban']; + } + + public function getBic() + { + return $this->getValues()['bic']; + } + + public function isCharitable() + { + return $this->getValues()['charitable']; + } + + public function getPresident() + { + return $this->getValues()['president']; + } + + } diff --git a/src/admin/views/test/tmpl/default.php b/src/admin/views/test/tmpl/default.php new file mode 100644 index 0000000..9c82d19 --- /dev/null +++ b/src/admin/views/test/tmpl/default.php @@ -0,0 +1,16 @@ + + +ClubName: club->getName(); ?>
+ClubName: club->getAddress(); ?>
+ClubName: club->getCity(); ?>
+ClubName: club->getHomepage(); ?>
+ClubName: club->getMail(); ?>
+ClubName: club->getIban(); ?>
+ClubName: club->getBic(); ?>
+ClubName: club->isCharitable(); ?>
+ClubName: club->getPresident(); ?>
diff --git a/src/admin/views/test/view.html.php b/src/admin/views/test/view.html.php new file mode 100644 index 0000000..dd1fc89 --- /dev/null +++ b/src/admin/views/test/view.html.php @@ -0,0 +1,35 @@ +clubs = $factory->loadElements(); + + $this->club = $this->clubs[0]; + + parent::display($tpl); + } + + protected function getViewName() + { + return 'club'; + } + protected function getElementController() + { + return new ClubsControllerClub(); + } + + +} From cb624c19ebdf2bd0e51f2fd63a14a9c9a5176cc9 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 22 May 2019 15:47:10 +0200 Subject: [PATCH 06/41] Added second class to debug 1:n relation, which is working in general --- src/admin/common/abstract/model.php | 8 +++++-- src/admin/common/models/factory/club.php | 2 +- src/admin/common/models/factory/user.php | 27 ++++++++++++++++++++++++ src/admin/common/models/user.php | 19 +++++++++++++++++ src/admin/views/test/tmpl/default.php | 2 +- 5 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 src/admin/common/models/factory/user.php create mode 100644 src/admin/common/models/user.php diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index e3c4ce8..9a1561f 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -141,10 +141,14 @@ abstract class AbstractCommonClubsModel private function loadExternalReferenceAsObject($className, $value) { + if(is_string($value) && preg_match('/^[0-9]+$/', $value)) + $value = (int) $value; + if(! is_int($value)) throw new Exception('Reference with non-integer value'); - $factory = $this->getFactoryOfClass($className); + $factoryName = $this->getFactoryNameOfClass($className); + $factory = new $factoryName(); return $factory->loadById($value); } @@ -157,7 +161,7 @@ abstract class AbstractCommonClubsModel * @param string $className * @return AbstractCommonClubsModelFactory */ - private function getFactoryOfClass($className) + private function getFactoryNameOfClass($className) { if(empty(self::CLASSNAME_MAP[$className])) { diff --git a/src/admin/common/models/factory/club.php b/src/admin/common/models/factory/club.php index 9df0d93..e10dd2c 100644 --- a/src/admin/common/models/factory/club.php +++ b/src/admin/common/models/factory/club.php @@ -16,7 +16,7 @@ class CommonClubsModelFactoryClub extends AbstractCommonClubsModelFactory 'iban'=>array(), 'bic'=>array(), 'charitable'=>array('type'=>'int'), - 'president'=>array('type'=>'int', 'ref'=>'CommonClubsModelUser') + 'president'=>array('type'=>'ref', 'ref'=>'CommonClubsModelUser') ); } diff --git a/src/admin/common/models/factory/user.php b/src/admin/common/models/factory/user.php new file mode 100644 index 0000000..7104187 --- /dev/null +++ b/src/admin/common/models/factory/user.php @@ -0,0 +1,27 @@ +array(), + 'name'=>array() + ); + } + + public function getTableName() + { + return '#__club_users'; + } + + public function getClassName() + { + return 'CommonClubsModelUser'; + } + + +} \ No newline at end of file diff --git a/src/admin/common/models/user.php b/src/admin/common/models/user.php new file mode 100644 index 0000000..ce8c821 --- /dev/null +++ b/src/admin/common/models/user.php @@ -0,0 +1,19 @@ +getValues()['name']; + } + + +} \ No newline at end of file diff --git a/src/admin/views/test/tmpl/default.php b/src/admin/views/test/tmpl/default.php index 9c82d19..008dd17 100644 --- a/src/admin/views/test/tmpl/default.php +++ b/src/admin/views/test/tmpl/default.php @@ -13,4 +13,4 @@ ClubName: club->getMail(); ?>
ClubName: club->getIban(); ?>
ClubName: club->getBic(); ?>
ClubName: club->isCharitable(); ?>
-ClubName: club->getPresident(); ?>
+ClubName: club->getPresident()->getName(); ?>
From 7c18f48b2a8da0717d94fde86805b89dfff07536 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 22 May 2019 16:07:01 +0200 Subject: [PATCH 07/41] 1:n relations have been successfully read. --- src/admin/common/abstract/model/factory.php | 2 ++ src/admin/common/models/club.php | 5 +++- src/admin/common/models/factory/place.php | 27 +++++++++++++++++++++ src/admin/common/models/place.php | 23 ++++++++++++++++++ src/admin/views/test/tmpl/default.php | 3 +++ src/admin/views/test/view.html.php | 6 +++++ 6 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/admin/common/models/factory/place.php create mode 100644 src/admin/common/models/place.php diff --git a/src/admin/common/abstract/model/factory.php b/src/admin/common/abstract/model/factory.php index d27f5c8..491799c 100644 --- a/src/admin/common/abstract/model/factory.php +++ b/src/admin/common/abstract/model/factory.php @@ -10,6 +10,8 @@ class ElementNotFoundException extends Exception abstract class AbstractCommonClubsModelFactory { + + // TODO Attribures as objects allowing to use polymophism in filtering/parsing/checking/sql building /* * This method should return an array to configure the trivially accessible values. diff --git a/src/admin/common/models/club.php b/src/admin/common/models/club.php index 024ebdb..d523b29 100644 --- a/src/admin/common/models/club.php +++ b/src/admin/common/models/club.php @@ -56,5 +56,8 @@ class CommonClubsModelClub extends AbstractCommonClubsModel return $this->getValues()['president']; } - + public function getPlaces() + { + return $this->fetchAssociatedElements(new CommonClubsModelFactoryPlace(), 'clubid'); + } } diff --git a/src/admin/common/models/factory/place.php b/src/admin/common/models/factory/place.php new file mode 100644 index 0000000..a08046e --- /dev/null +++ b/src/admin/common/models/factory/place.php @@ -0,0 +1,27 @@ +array(), + 'club'=>array('col'=>'clubid', 'type'=>'ref', 'ref'=>'CommonClubsModelClub') + ); + } + + public function getTableName() + { + return '#__club_places'; + } + + public function getClassName() + { + return 'CommonClubsModelPlace'; + } + + +} \ No newline at end of file diff --git a/src/admin/common/models/place.php b/src/admin/common/models/place.php new file mode 100644 index 0000000..c8619f3 --- /dev/null +++ b/src/admin/common/models/place.php @@ -0,0 +1,23 @@ +getValues()['name']; + } + + public function getClub() + { + return $this->getValues()['club']; + } + +} \ No newline at end of file diff --git a/src/admin/views/test/tmpl/default.php b/src/admin/views/test/tmpl/default.php index 008dd17..18076f7 100644 --- a/src/admin/views/test/tmpl/default.php +++ b/src/admin/views/test/tmpl/default.php @@ -14,3 +14,6 @@ ClubName: club->getIban(); ?>
ClubName: club->getBic(); ?>
ClubName: club->isCharitable(); ?>
ClubName: club->getPresident()->getName(); ?>
+club->getPlaces() as $place): ?> +Place: getName(); ?>
+ diff --git a/src/admin/views/test/view.html.php b/src/admin/views/test/view.html.php index dd1fc89..94eafec 100644 --- a/src/admin/views/test/view.html.php +++ b/src/admin/views/test/view.html.php @@ -19,6 +19,12 @@ class ClubsViewTest extends HtmlView $this->club = $this->clubs[0]; + $c = $this->club; + $p = $c->getPresident(); + $id = $p->getId(); + + $places = $c->getPlaces(); + parent::display($tpl); } From d48d7eb8538325f5572bc0ea6a5686935b6bf9c3 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 22 May 2019 16:16:26 +0200 Subject: [PATCH 08/41] Added support for optional columns in db --- src/admin/common/abstract/model.php | 7 +++++++ src/admin/common/abstract/model/factory.php | 1 + src/admin/common/models/factory/place.php | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 9a1561f..41829ef 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -117,6 +117,9 @@ abstract class AbstractCommonClubsModel if($v['type'] !== 'ref') continue; + if(is_null($vals[$k])) + continue; + $vals[$k] = $vals[$k]->getId(); } @@ -133,6 +136,9 @@ abstract class AbstractCommonClubsModel if(empty($v['ref'])) throw new Exception('External reference of unknown class found.'); + if(empty($vals[$k])) + continue; + $vals[$k] = $this->loadExternalReferenceAsObject($v['ref'], $vals[$k]); } @@ -304,6 +310,7 @@ abstract class AbstractCommonClubsModel $q->where("id = {$this->id}"); } + // FIXME Add additional filter to remove associations of the object public function delete() { $db = Factory::getDbo(); diff --git a/src/admin/common/abstract/model/factory.php b/src/admin/common/abstract/model/factory.php index 491799c..bb60ee9 100644 --- a/src/admin/common/abstract/model/factory.php +++ b/src/admin/common/abstract/model/factory.php @@ -30,6 +30,7 @@ abstract class AbstractCommonClubsModelFactory * - int * - float * - ref + * - optional: boolean, if true, the field can be NULL * - ref: (only with type='ref') The name of the class that is referenced */ public abstract function getAttributes(); diff --git a/src/admin/common/models/factory/place.php b/src/admin/common/models/factory/place.php index a08046e..c8f1bd8 100644 --- a/src/admin/common/models/factory/place.php +++ b/src/admin/common/models/factory/place.php @@ -9,7 +9,8 @@ class CommonClubsModelFactoryPlace extends AbstractCommonClubsModelFactory { return array( 'name'=>array(), - 'club'=>array('col'=>'clubid', 'type'=>'ref', 'ref'=>'CommonClubsModelClub') + 'club'=>array('col'=>'clubid', 'type'=>'ref', 'ref'=>'CommonClubsModelClub'), + 'area'=>array('type'=>'int', 'optional'=>true) ); } From ed47052a59051313a77a87594dc2c4851c969515 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 22 May 2019 16:18:09 +0200 Subject: [PATCH 09/41] Added functionality to allow to remove slave objects in database in case of deletion --- src/admin/common/abstract/model.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 41829ef..2d891d0 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -310,7 +310,9 @@ abstract class AbstractCommonClubsModel $q->where("id = {$this->id}"); } - // FIXME Add additional filter to remove associations of the object + protected function prepareDelete() + {} + public function delete() { $db = Factory::getDbo(); @@ -322,6 +324,7 @@ abstract class AbstractCommonClubsModel $q->where("id = {$this->id}"); $db->transactionStart(); + $this->prepareDelete(); $db->setQuery($q); $db->execute(); $db->transactionCommit(); From a9caa06e028fbe731a8bf788c29fa248324cbf0a Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 22 May 2019 16:32:09 +0200 Subject: [PATCH 10/41] Added code to simplify setting values --- src/admin/common/abstract/model.php | 8 ++++++++ src/admin/common/models/place.php | 14 ++++++++++++++ src/admin/views/test/view.html.php | 2 ++ 3 files changed, 24 insertions(+) diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 2d891d0..1d00ba2 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -45,6 +45,14 @@ abstract class AbstractCommonClubsModel $this->values = $values; } + protected function setValue($key, $value) + { + if(is_null($this->values)) + $this->loadDataFromDatabase(); + + $this->values[$key] = $value; + } + public function markAsNew($new) { $this->new = $new; diff --git a/src/admin/common/models/place.php b/src/admin/common/models/place.php index c8619f3..f64d134 100644 --- a/src/admin/common/models/place.php +++ b/src/admin/common/models/place.php @@ -20,4 +20,18 @@ class CommonClubsModelPlace extends AbstractCommonClubsModel return $this->getValues()['club']; } + public function getArea() + { + return $this->getValues()['area']; + } + + public function setName($name) + { + $this->setValue('name', $name); + } + + public function setArea($area) + { + $this->setValue('area', $area); + } } \ No newline at end of file diff --git a/src/admin/views/test/view.html.php b/src/admin/views/test/view.html.php index 94eafec..75186ca 100644 --- a/src/admin/views/test/view.html.php +++ b/src/admin/views/test/view.html.php @@ -24,6 +24,8 @@ class ClubsViewTest extends HtmlView $id = $p->getId(); $places = $c->getPlaces(); +// $places[0]->setName("abc"); +// $places[0]->save(); parent::display($tpl); } From 23941f32d3550feb8751d59e8e435d4d4aad127b Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 22 May 2019 16:32:21 +0200 Subject: [PATCH 11/41] Removed bug when saving NULL values --- src/admin/common/abstract/model.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 1d00ba2..1b1dab8 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -207,7 +207,10 @@ abstract class AbstractCommonClubsModel $v['type'] = 'string'; if($rawData[$k] === NULL) + { $quotedData[$k] = 'NULL'; + continue; + } switch($v['type']) { From 8b25925ec473230bf272b6f26aef99c2111eb1f0 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 23 May 2019 17:52:11 +0200 Subject: [PATCH 12/41] Successfully tested create, update and delete of trivial elements --- src/admin/common/abstract/model.php | 16 +++++++++++++--- src/admin/common/abstract/model/factory.php | 4 ++-- src/admin/common/models/place.php | 6 ++++++ src/admin/views/test/tmpl/default.php | 3 +++ src/admin/views/test/view.html.php | 16 ++++++++++++++++ 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 1b1dab8..3ecb62b 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -6,6 +6,9 @@ use Joomla\CMS\Factory; // No direct access. defined('_JEXEC') or die; +class AssociatedObjectUnsavedException extends Exception +{} + abstract class AbstractCommonClubsModel { // TODO Adddata validator @@ -27,9 +30,9 @@ abstract class AbstractCommonClubsModel private $values = null; - protected function getValues() + protected function getValues($force = false) { - if(is_null($this->values)) + if(is_null($this->values) || $force) $this->loadDataFromDatabase(); return $this->values; @@ -128,7 +131,14 @@ abstract class AbstractCommonClubsModel if(is_null($vals[$k])) continue; - $vals[$k] = $vals[$k]->getId(); + $id = $vals[$k]->getId(); + + if($id === 'new') + { + throw new AssociatedObjectUnsavedException(); + } + + $vals[$k] = $id; } return $vals; diff --git a/src/admin/common/abstract/model/factory.php b/src/admin/common/abstract/model/factory.php index bb60ee9..baccce6 100644 --- a/src/admin/common/abstract/model/factory.php +++ b/src/admin/common/abstract/model/factory.php @@ -36,9 +36,9 @@ abstract class AbstractCommonClubsModelFactory public abstract function getAttributes(); private $attributes = null; - private function fetchAttributes() + private function fetchAttributes($force = False) { - if($this->attributes === null) + if($this->attributes === null || $force) $this->attributes = $this->getAttributes(); return $this->attributes; diff --git a/src/admin/common/models/place.php b/src/admin/common/models/place.php index f64d134..0b55d8b 100644 --- a/src/admin/common/models/place.php +++ b/src/admin/common/models/place.php @@ -34,4 +34,10 @@ class CommonClubsModelPlace extends AbstractCommonClubsModel { $this->setValue('area', $area); } + + public function setClub($club) + { + $this->setValue('club', $club); + } + } \ No newline at end of file diff --git a/src/admin/views/test/tmpl/default.php b/src/admin/views/test/tmpl/default.php index 18076f7..90b91bb 100644 --- a/src/admin/views/test/tmpl/default.php +++ b/src/admin/views/test/tmpl/default.php @@ -17,3 +17,6 @@ ClubName: club->getPresident()->getName(); ?>
club->getPlaces() as $place): ?> Place: getName(); ?>
+ +

Output

+
log); ?>
\ No newline at end of file diff --git a/src/admin/views/test/view.html.php b/src/admin/views/test/view.html.php index 75186ca..23694b4 100644 --- a/src/admin/views/test/view.html.php +++ b/src/admin/views/test/view.html.php @@ -14,6 +14,8 @@ class ClubsViewTest extends HtmlView { ToolbarHelper::title('Test'); + $this->log = ''; + $factory = new CommonClubsModelFactoryClub(); $this->clubs = $factory->loadElements(); @@ -27,6 +29,20 @@ class ClubsViewTest extends HtmlView // $places[0]->setName("abc"); // $places[0]->save(); + $pfactory = new CommonClubsModelFactoryPlace(); + $np = $pfactory->createNew(); + $np->setName('MyName'); + $np->setClub($c); + //$np->save(); + $np->getId(); + + $np = $c->getPlaces()[1]; + $np->getName(); + $np->setName('foo2 with new Name'); +// $np->save(); +// $np->delete(); + $this->log = $np; + parent::display($tpl); } From e6db7787ef7dea49d0f1c3d1a8e2a0739c0fa880 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 23 May 2019 17:54:38 +0200 Subject: [PATCH 13/41] Enhanced deletion script to allow access to db object directly. This allows the user to choose wheather better issue plain SQL or operate on the objects (suggested) --- src/admin/common/abstract/model.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 3ecb62b..4448ac6 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -331,7 +331,7 @@ abstract class AbstractCommonClubsModel $q->where("id = {$this->id}"); } - protected function prepareDelete() + protected function prepareDelete($db) {} public function delete() @@ -345,7 +345,7 @@ abstract class AbstractCommonClubsModel $q->where("id = {$this->id}"); $db->transactionStart(); - $this->prepareDelete(); + $this->prepareDelete($db); $db->setQuery($q); $db->execute(); $db->transactionCommit(); From 7bf2386851c17f2f1c424b04e08324de475941d1 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 23 May 2019 18:05:16 +0200 Subject: [PATCH 14/41] Make deletion atomic using transactions even in case of recursive deletions --- src/admin/common/abstract/model.php | 4 ++-- src/admin/views/test/view.html.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 4448ac6..c24aa46 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -344,11 +344,11 @@ abstract class AbstractCommonClubsModel $q->delete($factory->getTableName()); $q->where("id = {$this->id}"); - $db->transactionStart(); + $db->transactionStart(true); $this->prepareDelete($db); $db->setQuery($q); $db->execute(); - $db->transactionCommit(); + $db->transactionCommit(true); } /** diff --git a/src/admin/views/test/view.html.php b/src/admin/views/test/view.html.php index 23694b4..d6049fa 100644 --- a/src/admin/views/test/view.html.php +++ b/src/admin/views/test/view.html.php @@ -21,7 +21,7 @@ class ClubsViewTest extends HtmlView $this->club = $this->clubs[0]; - $c = $this->club; + $c = $factory->loadById(1); $p = $c->getPresident(); $id = $p->getId(); @@ -33,12 +33,12 @@ class ClubsViewTest extends HtmlView $np = $pfactory->createNew(); $np->setName('MyName'); $np->setClub($c); - //$np->save(); +// $np->save(); $np->getId(); - $np = $c->getPlaces()[1]; + $np = $c->getPlaces()[2]; $np->getName(); - $np->setName('foo2 with new Name'); +// $np->setName('foo2 with new Name'); // $np->save(); // $np->delete(); $this->log = $np; From dec4bb5165c3eb8c20493196a7e66b58c7302e21 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 23 May 2019 18:15:22 +0200 Subject: [PATCH 15/41] Changed cacheing of attributes to be useful --- src/admin/common/abstract/model/factory.php | 8 ++++---- src/admin/common/models/factory/club.php | 2 +- src/admin/common/models/factory/place.php | 2 +- src/admin/common/models/factory/user.php | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/admin/common/abstract/model/factory.php b/src/admin/common/abstract/model/factory.php index baccce6..1155c6c 100644 --- a/src/admin/common/abstract/model/factory.php +++ b/src/admin/common/abstract/model/factory.php @@ -33,13 +33,13 @@ abstract class AbstractCommonClubsModelFactory * - optional: boolean, if true, the field can be NULL * - ref: (only with type='ref') The name of the class that is referenced */ - public abstract function getAttributes(); + protected abstract function fetchAttributes(); private $attributes = null; - private function fetchAttributes($force = False) + public function getAttributes($force = False) { if($this->attributes === null || $force) - $this->attributes = $this->getAttributes(); + $this->attributes = $this->fetchAttributes(); return $this->attributes; } @@ -128,7 +128,7 @@ abstract class AbstractCommonClubsModelFactory $attribs = array_map(function($v){ return Null; - }, $this->fetchAttributes()); + }, $this->getAttributes()); $obj->setValues($attribs); $obj->fillDefaultValues(); diff --git a/src/admin/common/models/factory/club.php b/src/admin/common/models/factory/club.php index e10dd2c..d3ee0c8 100644 --- a/src/admin/common/models/factory/club.php +++ b/src/admin/common/models/factory/club.php @@ -5,7 +5,7 @@ defined('_JEXEC') or die; class CommonClubsModelFactoryClub extends AbstractCommonClubsModelFactory { - public function getAttributes() + public function fetchAttributes() { return array( 'name'=>array(), diff --git a/src/admin/common/models/factory/place.php b/src/admin/common/models/factory/place.php index c8f1bd8..fcae159 100644 --- a/src/admin/common/models/factory/place.php +++ b/src/admin/common/models/factory/place.php @@ -5,7 +5,7 @@ defined('_JEXEC') or die; class CommonClubsModelFactoryPlace extends AbstractCommonClubsModelFactory { - public function getAttributes() + public function fetchAttributes() { return array( 'name'=>array(), diff --git a/src/admin/common/models/factory/user.php b/src/admin/common/models/factory/user.php index 7104187..5c55bea 100644 --- a/src/admin/common/models/factory/user.php +++ b/src/admin/common/models/factory/user.php @@ -5,7 +5,7 @@ defined('_JEXEC') or die; class CommonClubsModelFactoryUser extends AbstractCommonClubsModelFactory { - public function getAttributes() + public function fetchAttributes() { return array( 'user'=>array(), From 92481a62ac0402fc3612ebd319ddb738654f7057 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 24 May 2019 13:33:14 +0200 Subject: [PATCH 16/41] Added code to git in order to push for the weekend --- src/admin/common/abstract/model/factory.php | 38 +++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/admin/common/abstract/model/factory.php b/src/admin/common/abstract/model/factory.php index 1155c6c..fc0b52b 100644 --- a/src/admin/common/abstract/model/factory.php +++ b/src/admin/common/abstract/model/factory.php @@ -47,6 +47,44 @@ abstract class AbstractCommonClubsModelFactory public abstract function getTableName(); public abstract function getClassName(); + // TODO Als Klassen formulieren + /* + * This method returns the relevant join operations on the data. + * The return value is an associated arrray whose keys are the names of the tables in the join. + * Each value of such joins is again an associative array. + * These arrays have the following entries: + * - alias: The alias name of the table to use. Mandatory + * - type: The type of the join. Can be inner, left, right or outer. Defaults to inner. + * - on: Join constraint as a string representation. + * - cols: The columns to insert in the query. Same structure as fetchAttributes() method + * + * Example: + * array( + * 'mytable'=>array( + * 'alias'=>'t1', + * 'type'=>'left', + * 'on'=>'main.id = t1.clubid', + * 'cols'=>array( + * 'name'=>array(), + * 'count'=>array('type'=>'int', 'col'=>'numElements' + * ) + * ) + * ) + */ + protected function fetchJoins() + { + return array(); + } + + private $joins = null; + public function getAttributes($force = False) + { + if($this->joins === null || $force) + $this->joins = $this->fetchJoins(); + + return $this->joins; + } + /** * * @param string $condition From 5a359ad97a2793013a58a2e6e0ef498cbc303942 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sun, 26 May 2019 16:15:20 +0200 Subject: [PATCH 17/41] Small changes in the code to make something run again... --- .buildpath | 1 + .settings/org.eclipse.php.core.prefs | 2 +- src/admin/common/abstract/model/factory.php | 2 +- src/admin/views/club/view.html.php | 21 +++++++----- src/admin/views/clubs/view.html.php | 3 +- src/admin/views/test/view.html.php | 36 ++++++++++----------- 6 files changed, 36 insertions(+), 29 deletions(-) diff --git a/.buildpath b/.buildpath index 635eec4..2c3078b 100644 --- a/.buildpath +++ b/.buildpath @@ -8,4 +8,5 @@ + diff --git a/.settings/org.eclipse.php.core.prefs b/.settings/org.eclipse.php.core.prefs index d0241b4..16ec813 100644 --- a/.settings/org.eclipse.php.core.prefs +++ b/.settings/org.eclipse.php.core.prefs @@ -1,2 +1,2 @@ eclipse.preferences.version=1 -include_path=1;/srv/slt-dev +include_path=1;/srv/slt-dev\u00051;/srv/http/slt diff --git a/src/admin/common/abstract/model/factory.php b/src/admin/common/abstract/model/factory.php index fc0b52b..a0f2aac 100644 --- a/src/admin/common/abstract/model/factory.php +++ b/src/admin/common/abstract/model/factory.php @@ -77,7 +77,7 @@ abstract class AbstractCommonClubsModelFactory } private $joins = null; - public function getAttributes($force = False) + public function getJoins($force = False) { if($this->joins === null || $force) $this->joins = $this->fetchJoins(); diff --git a/src/admin/views/club/view.html.php b/src/admin/views/club/view.html.php index 1d1e477..9b0b323 100644 --- a/src/admin/views/club/view.html.php +++ b/src/admin/views/club/view.html.php @@ -15,17 +15,22 @@ class ClubsViewClub extends AbstractClubsViewSingle $this->prepareDisplay(); - $this->users = ClubsUser::loadUsers(); + $userFactory = new CommonClubsModelFactoryUser(); + $this->users = $userFactory->loadElements(); - if(! $this->isNew) + if($this->isNew) { - $offers = ClubsOffer::loadOffers(); - $currentOffers = $this->object->getOffers(); - $this->offers = array_map(function($offer) use ($currentOffers){ - $mark = False; - return array('offer'=>$offer, 'mark'=>$mark); - }, $offers); + } + else + { +// $offers = ClubsOffer::loadOffers(); +// $currentOffers = $this->object->getOffers(); + +// $this->offers = array_map(function($offer) use ($currentOffers){ +// $mark = False; +// return array('offer'=>$offer, 'mark'=>$mark); +// }, $offers); } parent::display($tpl); diff --git a/src/admin/views/clubs/view.html.php b/src/admin/views/clubs/view.html.php index 09de821..9124741 100644 --- a/src/admin/views/clubs/view.html.php +++ b/src/admin/views/clubs/view.html.php @@ -12,7 +12,8 @@ class ClubsViewClubs extends HtmlView function display($tpl = null) { // $this->offers = ClubsOffer::loadOffers(); - $this->clubs = ClubsClub::loadClubs(); + $factory = new CommonClubsModelFactoryClub(); + $this->clubs = $factory->loadElements(); ToolbarHelper::title('Club-Management - Clubs'); parent::display($tpl); diff --git a/src/admin/views/test/view.html.php b/src/admin/views/test/view.html.php index d6049fa..81e77f1 100644 --- a/src/admin/views/test/view.html.php +++ b/src/admin/views/test/view.html.php @@ -21,27 +21,27 @@ class ClubsViewTest extends HtmlView $this->club = $this->clubs[0]; - $c = $factory->loadById(1); - $p = $c->getPresident(); - $id = $p->getId(); +// $c = $factory->loadById(1); +// $p = $c->getPresident(); +// $id = $p->getId(); - $places = $c->getPlaces(); -// $places[0]->setName("abc"); -// $places[0]->save(); +// $places = $c->getPlaces(); +// // $places[0]->setName("abc"); +// // $places[0]->save(); - $pfactory = new CommonClubsModelFactoryPlace(); - $np = $pfactory->createNew(); - $np->setName('MyName'); - $np->setClub($c); -// $np->save(); - $np->getId(); +// $pfactory = new CommonClubsModelFactoryPlace(); +// $np = $pfactory->createNew(); +// $np->setName('MyName'); +// $np->setClub($c); +// // $np->save(); +// $np->getId(); - $np = $c->getPlaces()[2]; - $np->getName(); -// $np->setName('foo2 with new Name'); -// $np->save(); -// $np->delete(); - $this->log = $np; +// $np = $c->getPlaces()[2]; +// $np->getName(); +// // $np->setName('foo2 with new Name'); +// // $np->save(); +// // $np->delete(); +// $this->log = $np; parent::display($tpl); } From 8dbbb4d245575335887de4c3110acedc0a25999c Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Tue, 28 May 2019 11:13:39 +0200 Subject: [PATCH 18/41] Updated settings file --- .settings/org.eclipse.php.core.prefs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.settings/org.eclipse.php.core.prefs b/.settings/org.eclipse.php.core.prefs index 16ec813..d8b1429 100644 --- a/.settings/org.eclipse.php.core.prefs +++ b/.settings/org.eclipse.php.core.prefs @@ -1,2 +1,2 @@ eclipse.preferences.version=1 -include_path=1;/srv/slt-dev\u00051;/srv/http/slt +include_path=1;/srv/slt-dev\u00051;/srv/http/slt\u00051;/home/private/slt/joomla From d93a02e779ab57064b25a5147b90410a961e08bb Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 29 May 2019 17:55:30 +0200 Subject: [PATCH 19/41] Made definition of columns based on objects --- src/admin/common/abstract/model.php | 126 +++----------------- src/admin/common/abstract/model/column.php | 74 ++++++++++++ src/admin/common/abstract/model/factory.php | 17 ++- src/admin/common/models/column/float.php | 22 ++++ src/admin/common/models/column/int.php | 22 ++++ src/admin/common/models/column/ref.php | 105 ++++++++++++++++ src/admin/common/models/column/string.php | 22 ++++ src/admin/common/models/factory/club.php | 18 +-- src/admin/common/models/factory/place.php | 6 +- src/admin/common/models/factory/user.php | 4 +- 10 files changed, 291 insertions(+), 125 deletions(-) create mode 100644 src/admin/common/abstract/model/column.php create mode 100644 src/admin/common/models/column/float.php create mode 100644 src/admin/common/models/column/int.php create mode 100644 src/admin/common/models/column/ref.php create mode 100644 src/admin/common/models/column/string.php diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index c24aa46..2a7dce1 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -6,12 +6,9 @@ use Joomla\CMS\Factory; // No direct access. defined('_JEXEC') or die; -class AssociatedObjectUnsavedException extends Exception -{} - abstract class AbstractCommonClubsModel { - // TODO Adddata validator + // TODO Add data validator // TODO Make setting of values attribute fail in case of problems // FIXME Add Joins in select statements @@ -103,10 +100,9 @@ abstract class AbstractCommonClubsModel $db = Factory::getDbo(); $q = $db->getQuery(true); - foreach($attribs as $k => $v) + foreach($attribs as $a) { - $rawColName = isset($v['col']) ? $v['col'] : $k; - $q->select($q->qn($rawColName, $k)); + $a->select($q); } $q->from($factory->getTableName()); $q->where("id = {$this->id}"); @@ -123,22 +119,10 @@ abstract class AbstractCommonClubsModel private function packExternalReferencesAsKeys($vals) { - foreach($this->getFactory()->getAttributes() as $k => $v) + foreach($this->getFactory()->getAttributes() as $a) { - if($v['type'] !== 'ref') - continue; - - if(is_null($vals[$k])) - continue; - - $id = $vals[$k]->getId(); - - if($id === 'new') - { - throw new AssociatedObjectUnsavedException(); - } - - $vals[$k] = $id; + $alias = $a->getAlias(); + $vals[$alias] = $a->packValue($vals[$alias]); } return $vals; @@ -146,64 +130,20 @@ abstract class AbstractCommonClubsModel private function unpackExternalReferencesFromKeys($vals) { - foreach($this->getFactory()->getAttributes() as $k => $v) + foreach($this->getFactory()->getAttributes() as $a) { - if(empty($v['type']) || $v['type'] !== 'ref') - continue; - - if(empty($v['ref'])) - throw new Exception('External reference of unknown class found.'); - - if(empty($vals[$k])) - continue; - - $vals[$k] = $this->loadExternalReferenceAsObject($v['ref'], $vals[$k]); + $alias = $a->getAlias(); + $vals[$alias] = $a->unpackValue($vals[$alias]); } return $vals; } - private function loadExternalReferenceAsObject($className, $value) - { - if(is_string($value) && preg_match('/^[0-9]+$/', $value)) - $value = (int) $value; - - if(! is_int($value)) - throw new Exception('Reference with non-integer value'); - - $factoryName = $this->getFactoryNameOfClass($className); - $factory = new $factoryName(); - return $factory->loadById($value); - } - - private const CLASSNAME_MAP = array( - - ); - - /** - * @todo This must be done better and more cleanly - * @param string $className - * @return AbstractCommonClubsModelFactory - */ - private function getFactoryNameOfClass($className) - { - if(empty(self::CLASSNAME_MAP[$className])) - { - $parts = array(); - if(preg_match('/^CommonClubsModel(.*)/', $className, $parts)) - { - return "CommonClubsModelFactory{$parts[1]}"; - } - throw new Exception("Unknown mapping of class $className"); - } - - return self::CLASSNAME_MAP[$className]; - } /** * * @param array $rawData - * @param array $attribs + * @param AbstractCommonClubsModelColumn[] $attribs * @param JDatabaseQuery $q * @return string[]|number[]|NULL[] */ @@ -211,38 +151,10 @@ abstract class AbstractCommonClubsModel { $quotedData = array(); - foreach($attribs as $k => $v) + foreach($attribs as $a) { - if(empty($v['type'])) - $v['type'] = 'string'; - - if($rawData[$k] === NULL) - { - $quotedData[$k] = 'NULL'; - continue; - } - - switch($v['type']) - { - case 'string': - $quotedData[$k] = $q->q($rawData[$k]); - break; - - case 'int': - $quotedData[$k] = (int) $rawData[$k]; - break; - - case 'float': - $quotedData[$k] = (float) $rawData[$k]; - break; - - case 'ref': - if($v['ref'] === null) - $quotedData[$k] = 'NULL'; - else - $quotedData[$k] = $rawData[$k]->getId(); - break; - } + $alias = $a->getAlias(); + $quotedData[$alias] = $a->getQuotedValue($q, $rawData[$alias]); } return $quotedData; @@ -277,7 +189,7 @@ abstract class AbstractCommonClubsModel /** * - * @param array $attribs + * @param AbstractCommonClubsModelColumn[] $attribs * @param AbstractCommonClubsModelFactory $factory * @param JDatabaseQuery $q */ @@ -286,9 +198,9 @@ abstract class AbstractCommonClubsModel $q->insert($factory->getTableName()); $dbcols = array(); - foreach($attribs as $k => $v) + foreach($attribs as $a) { - $dbcols[] = isset($v['col']) ? $v['col'] : $k; + $dbcols[] = $a->getColumn(); } $q->columns($q->qn($dbcols)); @@ -310,7 +222,7 @@ abstract class AbstractCommonClubsModel /** * - * @param array $attribs + * @param AbstractCommonClubsModelColumn[] $attribs * @param AbstractCommonClubsModelFactory $factory * @param JDatabaseQuery $q */ @@ -319,8 +231,8 @@ abstract class AbstractCommonClubsModel $q->update($factory->getTableName()); $dbcols = array(); - foreach($attribs as $k => $v) - $dbcols[] = isset($v['col']) ? $v['col'] : $k; + foreach($attribs as $a) + $dbcols[] = $a->getColumn(); $quotedData = $this->getQuotedData($attribs, $q); diff --git a/src/admin/common/abstract/model/column.php b/src/admin/common/abstract/model/column.php new file mode 100644 index 0000000..a21ceee --- /dev/null +++ b/src/admin/common/abstract/model/column.php @@ -0,0 +1,74 @@ +alias; + } + + public function getColumn() + { + return $this->column; + } + + public function isRequired() + { + return $this->required; + } + + public abstract function isSimpleType(); + + public function __construct($alias, $required = true, $column = null) + { + $this->alias = $alias; + $this->required = $required; + if(isset($column)) + $this->column = $column; + else + $this->column = $alias; + } + + /** + * @param JDatabaseQuery $q + */ + public function select($q) + { + $q->select($q->qn($this->column, $this->alias)); + } + + /** + * + * @param JDatabaseQuery $q + * @param mixed $value + */ + public abstract function getQuotedValue($q,$value); + + /** + * + * @param JDatabaseQuery $q + */ + public function getQuotedColumnName($q) + { + return $q->qn($this->column); + } + + public function packValue($value) + { + return $value; + } + + public function unpackValue($value) + { + return $value; + } + +} diff --git a/src/admin/common/abstract/model/factory.php b/src/admin/common/abstract/model/factory.php index a0f2aac..391ad64 100644 --- a/src/admin/common/abstract/model/factory.php +++ b/src/admin/common/abstract/model/factory.php @@ -36,6 +36,10 @@ abstract class AbstractCommonClubsModelFactory protected abstract function fetchAttributes(); private $attributes = null; + /** + * @param boolean $force + * @return AbstractCommonClubsModelColumn[] + */ public function getAttributes($force = False) { if($this->attributes === null || $force) @@ -164,10 +168,15 @@ abstract class AbstractCommonClubsModelFactory $obj = $this->generatePlainObject('new'); $obj->markAsNew(true); - $attribs = array_map(function($v){ - return Null; - }, $this->getAttributes()); - $obj->setValues($attribs); + $values = array(); + foreach($this->getAttributes() as $a) + { + $values[$a->getAlias()] = null; + } +// $attribs = array_map(function($v){ +// return Null; +// }, $this->getAttributes()); + $obj->setValues($values); $obj->fillDefaultValues(); diff --git a/src/admin/common/models/column/float.php b/src/admin/common/models/column/float.php new file mode 100644 index 0000000..be81642 --- /dev/null +++ b/src/admin/common/models/column/float.php @@ -0,0 +1,22 @@ +className = $className; + } + + public function isSimpleType() + { + return false; + } + + public function getQuotedValue($q, $value) + { + if(is_null($value)) + return 'NULL'; + else + { + if(! ( $value instanceof $this->className ) ) + throw new WrongRefTypeException(); + + $id = $value->getId(); + if($id === 'new') + throw new AssociatedObjectUnsavedException(); + + return $id; + } + } + + public function packValue($value) + { + if(is_null($value)) + return null; + + if(! ($value instanceof $this->className) ) + throw new WrongRefTypeException(); + + $id = $value->getId(); + + if($id === 'new') + throw new AssociatedObjectUnsavedException(); + + return (int) $id; + } + + public function unpackValue($value) + { + if(empty($value)) + return null; + + if(is_string($value) && preg_match('/^[0-9]+$/', $value)) + $value = (int) $value; + + if(! is_int($value)) + throw new Exception('Reference with non-integer value'); + + $factoryName = $this->getFactoryNameOfClass($this->className); // XXX Use attribute? + $factory = new $factoryName(); + return $factory->loadById($value); + } + + private const CLASSNAME_MAP = array( + + ); + + /** + * @todo This must be done better and more cleanly + * @param string $className + * @return AbstractCommonClubsModelFactory + */ + private function getFactoryNameOfClass($className) + { + if(empty(self::CLASSNAME_MAP[$className])) + { + $parts = array(); + if(preg_match('/^CommonClubsModel(.*)/', $className, $parts)) + { + return "CommonClubsModelFactory{$parts[1]}"; + } + throw new Exception("Unknown mapping of class $className"); + } + + return self::CLASSNAME_MAP[$className]; + } + +} diff --git a/src/admin/common/models/column/string.php b/src/admin/common/models/column/string.php new file mode 100644 index 0000000..fe2140f --- /dev/null +++ b/src/admin/common/models/column/string.php @@ -0,0 +1,22 @@ +q($value); + } + + +} diff --git a/src/admin/common/models/factory/club.php b/src/admin/common/models/factory/club.php index d3ee0c8..6331ad6 100644 --- a/src/admin/common/models/factory/club.php +++ b/src/admin/common/models/factory/club.php @@ -8,15 +8,15 @@ class CommonClubsModelFactoryClub extends AbstractCommonClubsModelFactory public function fetchAttributes() { return array( - 'name'=>array(), - 'address'=>array(), - 'city'=>array(), - 'homepage'=>array(), - 'mail'=>array(), - 'iban'=>array(), - 'bic'=>array(), - 'charitable'=>array('type'=>'int'), - 'president'=>array('type'=>'ref', 'ref'=>'CommonClubsModelUser') + new CommonClubsModelColumnString('name'), + new CommonClubsModelColumnString('address'), + new CommonClubsModelColumnString('city'), + new CommonClubsModelColumnString('homepage'), + new CommonClubsModelColumnString('mail'), + new CommonClubsModelColumnString('iban'), + new CommonClubsModelColumnString('bic'), + new CommonClubsModelColumnInt('charitable'), + new CommonClubsModelColumnRef('president', 'CommonClubsModelUser') ); } diff --git a/src/admin/common/models/factory/place.php b/src/admin/common/models/factory/place.php index fcae159..01b40c6 100644 --- a/src/admin/common/models/factory/place.php +++ b/src/admin/common/models/factory/place.php @@ -8,9 +8,9 @@ class CommonClubsModelFactoryPlace extends AbstractCommonClubsModelFactory public function fetchAttributes() { return array( - 'name'=>array(), - 'club'=>array('col'=>'clubid', 'type'=>'ref', 'ref'=>'CommonClubsModelClub'), - 'area'=>array('type'=>'int', 'optional'=>true) + new CommonClubsModelColumnString('name'), + new CommonClubsModelColumnRef('club', 'CommonClubsModelClub', true, 'clubid'), + new CommonClubsModelColumnInt('area', false) ); } diff --git a/src/admin/common/models/factory/user.php b/src/admin/common/models/factory/user.php index 5c55bea..437bd73 100644 --- a/src/admin/common/models/factory/user.php +++ b/src/admin/common/models/factory/user.php @@ -8,8 +8,8 @@ class CommonClubsModelFactoryUser extends AbstractCommonClubsModelFactory public function fetchAttributes() { return array( - 'user'=>array(), - 'name'=>array() + new CommonClubsModelColumnString('user'), + new CommonClubsModelColumnString('name') ); } From 4fa01d4cc0e7093286bc7197e5f7b5448474c63c Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 29 May 2019 18:11:14 +0200 Subject: [PATCH 20/41] Added functionality to create joins using OOD --- src/admin/common/abstract/model/join.php | 62 ++++++++++++++++++++++++ src/admin/common/models/join/innner.php | 12 +++++ src/admin/common/models/join/left.php | 12 +++++ src/admin/common/models/join/outer.php | 12 +++++ src/admin/common/models/join/right.php | 12 +++++ 5 files changed, 110 insertions(+) create mode 100644 src/admin/common/abstract/model/join.php create mode 100644 src/admin/common/models/join/innner.php create mode 100644 src/admin/common/models/join/left.php create mode 100644 src/admin/common/models/join/outer.php create mode 100644 src/admin/common/models/join/right.php diff --git a/src/admin/common/abstract/model/join.php b/src/admin/common/abstract/model/join.php new file mode 100644 index 0000000..e039a99 --- /dev/null +++ b/src/admin/common/abstract/model/join.php @@ -0,0 +1,62 @@ +alias; + } + + public function getTableName() + { + return $this->tablename; + } + + public function getOnCondition() + { + return $this->condition; + } + + public function getColumns() + { + return $this->columns; + } + + /** + * + * @param JDatabaseQuery $q + */ + public function join($q) + { + $str = "{$this->tablename} AS {$this->alias}"; + if(isset($this->condition)) + $str .= " ON {$this->condition}"; + $this->addJoin($q, $str); + + foreach($this->columns as $c) + { + $qc = $q->q("{$this->alias}." . $c->getColumn(), $c->getAlias()); + $q->select($qc); + } + } + + /** + * + * @param JDatabaseQuery $q + * @param String $str + */ + protected abstract function addJoin($q, $str); + +} diff --git a/src/admin/common/models/join/innner.php b/src/admin/common/models/join/innner.php new file mode 100644 index 0000000..4b30dbd --- /dev/null +++ b/src/admin/common/models/join/innner.php @@ -0,0 +1,12 @@ +innerJoin($str); + } +} diff --git a/src/admin/common/models/join/left.php b/src/admin/common/models/join/left.php new file mode 100644 index 0000000..dfaa740 --- /dev/null +++ b/src/admin/common/models/join/left.php @@ -0,0 +1,12 @@ +leftJoin($str); + } +} diff --git a/src/admin/common/models/join/outer.php b/src/admin/common/models/join/outer.php new file mode 100644 index 0000000..dfe527d --- /dev/null +++ b/src/admin/common/models/join/outer.php @@ -0,0 +1,12 @@ +outerJoin($str); + } +} diff --git a/src/admin/common/models/join/right.php b/src/admin/common/models/join/right.php new file mode 100644 index 0000000..9758e17 --- /dev/null +++ b/src/admin/common/models/join/right.php @@ -0,0 +1,12 @@ +rightJoin($str); + } +} From 4ce8fd274d0a05ec63835f7e7258c0457d2cc441 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 31 May 2019 14:18:34 +0200 Subject: [PATCH 21/41] Created models for all database objects --- src/admin/common/abstract/model.php | 41 ++++- src/admin/common/abstract/model/factory.php | 18 +- src/admin/common/abstract/model/join.php | 22 +++ src/admin/common/models/club.php | 54 ++++++ src/admin/common/models/factory/offer.php | 24 +++ .../common/models/factory/offerassoc.php | 25 +++ src/admin/common/models/factory/position.php | 25 +++ src/admin/common/models/factory/user.php | 10 +- src/admin/common/models/factory/userassoc.php | 33 ++++ src/admin/common/models/offer.php | 37 +++++ src/admin/common/models/offerassoc.php | 49 ++++++ src/admin/common/models/position.php | 32 ++++ src/admin/common/models/user.php | 156 +++++++++++++++++- src/admin/common/models/userassoc.php | 119 +++++++++++++ 14 files changed, 634 insertions(+), 11 deletions(-) create mode 100644 src/admin/common/models/factory/offer.php create mode 100644 src/admin/common/models/factory/offerassoc.php create mode 100644 src/admin/common/models/factory/position.php create mode 100644 src/admin/common/models/factory/userassoc.php create mode 100644 src/admin/common/models/offer.php create mode 100644 src/admin/common/models/offerassoc.php create mode 100644 src/admin/common/models/position.php create mode 100644 src/admin/common/models/userassoc.php diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 2a7dce1..90babdc 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -107,6 +107,12 @@ abstract class AbstractCommonClubsModel $q->from($factory->getTableName()); $q->where("id = {$this->id}"); + $joins = $factory->getJoins(); + foreach($joins as $j) + { + $j->join($q); + } + $db->setQuery($q); $db->execute(); @@ -130,12 +136,20 @@ abstract class AbstractCommonClubsModel private function unpackExternalReferencesFromKeys($vals) { - foreach($this->getFactory()->getAttributes() as $a) + $factory = $this->getFactory(); + + foreach($factory->getAttributes() as $a) { $alias = $a->getAlias(); $vals[$alias] = $a->unpackValue($vals[$alias]); } + $joins = $factory->getJoins(); + foreach($joins as $join) + { + $join->unpackExternalReferencesFromKeys($vals); + } + return $vals; } @@ -243,6 +257,10 @@ abstract class AbstractCommonClubsModel $q->where("id = {$this->id}"); } + /** + * + * @param JDatabaseDriver $db + */ protected function prepareDelete($db) {} @@ -267,11 +285,28 @@ abstract class AbstractCommonClubsModel * * @param AbstractCommonClubsModelFactory $factory * @param string $colName + * @param array $constraints */ - protected function fetchAssociatedElements($factory, $colName) + protected function fetchAssociatedElements($factory, $colName, $constraints = null, $sorting = null) { $condition = "main.$colName = {$this->id}"; - return $factory->loadElements($condition); + + if(isset($constraints)) + { + if(is_array($constraints)) + $allConstraints = clone $constraints; + elseif(is_string($constraints)) + $allConstraints = array($constraints); + else + throw new Exception('Unknown type of constraint'); + + // Add the manual condition to match the current object + $allConstraints[] = $condition; + + return $factory->loadElements($allConstraints, $sorting); + } + else + $factory->loadElements($condition, $sorting); } protected function filterPackData($values) diff --git a/src/admin/common/abstract/model/factory.php b/src/admin/common/abstract/model/factory.php index 391ad64..1513bcd 100644 --- a/src/admin/common/abstract/model/factory.php +++ b/src/admin/common/abstract/model/factory.php @@ -81,20 +81,24 @@ abstract class AbstractCommonClubsModelFactory } private $joins = null; + /** + * @return AbstractCommonClubsModelJoin[] + */ public function getJoins($force = False) { if($this->joins === null || $force) $this->joins = $this->fetchJoins(); - return $this->joins; + return $this->joins; } /** * * @param string $condition + * @param string|array $sorting * @return array */ - public function loadElements($condition = null) + public function loadElements($condition = null, $sorting = null, $callback = null) { $db = Factory::getDbo(); $q = $db->getQuery(true); @@ -109,11 +113,17 @@ abstract class AbstractCommonClubsModelFactory $q->select('main.id AS id');//->select($columns); $q->from($this->getTableName() . ' AS main'); - // TODO Joins - if($condition !== null) $q->where($condition); + if($sorting !== null) + $q->order($sorting); + + if($callback !== null) + { + $callback($q); + } + $db->setQuery($q); $db->execute(); diff --git a/src/admin/common/abstract/model/join.php b/src/admin/common/abstract/model/join.php index e039a99..878f146 100644 --- a/src/admin/common/abstract/model/join.php +++ b/src/admin/common/abstract/model/join.php @@ -14,6 +14,19 @@ abstract class AbstractCommonClubsModelJoin */ protected $columns; + public function __construct($alias, $condition, $colums, $tablename = null) + { + /// XXX Some checks need to be performed + $this->alias = $alias; + $this->condition = $condition; + $this->columns = $colums; + + if(empty($tablename)) + $this->tablename = $alias; + else + $this->tablename = $alias; + } + public function getAlias() { return $this->alias; @@ -59,4 +72,13 @@ abstract class AbstractCommonClubsModelJoin */ protected abstract function addJoin($q, $str); + public function unpackExternalReferencesFromKeys(&$vals) + { + foreach($this->columns as $col) + { + $alias = $col->getAlias(); + $vals[$alias] = $col->unpackValue($vals[$alias]); + } + } + } diff --git a/src/admin/common/models/club.php b/src/admin/common/models/club.php index d523b29..94eea67 100644 --- a/src/admin/common/models/club.php +++ b/src/admin/common/models/club.php @@ -60,4 +60,58 @@ class CommonClubsModelClub extends AbstractCommonClubsModel { return $this->fetchAssociatedElements(new CommonClubsModelFactoryPlace(), 'clubid'); } + + public function getOffers() + { + $assocs = $this->fetchAssociatedElements(new CommonClubsModelFactoryOfferassocs(), 'clubid'); + + $offersFactory = new CommonClubsModelFactoryOffer(); + $allOffers = $offersFactory->loadElements(); + + $ret = array(); + foreach($allOffers as $offer) + { + $arr = array( + 'offer' => $offer, + 'valid' => false + ); + + $id = $offer->getId(); + foreach($assocs as $a) + { + $oid = $a->getOffer()->getId(); + if($id === $oid) + { + $arr['valid'] = true; + break; + } + } + + $ret[] = $arr; + } + return $ret; + } + + public function getUsers() + { + return $this->fetchAssociatedElements(new CommonClubsModelFactoryUserassoc(), 'clubid'); + } + + protected function prepareDelete($db) + { + $q = $db->getQuery(true); + $q->delete('#__club_user_assocs')->where("clubid = {$this->getId()}"); + $db->setQuery($q); + $db->execute(); + + $q = $db->getQuery(true); + $q->delete('#__club_places')->where("clubid = {$this->getId()}"); + $db->setQuery($q); + $db->execute(); + + $q = $db->getQuery(true); + $q->delete('#__club_offer_assocs')->where("clubid = {$this->getId()}"); + $db->setQuery($q); + $db->execute(); + } } diff --git a/src/admin/common/models/factory/offer.php b/src/admin/common/models/factory/offer.php new file mode 100644 index 0000000..4f3a875 --- /dev/null +++ b/src/admin/common/models/factory/offer.php @@ -0,0 +1,24 @@ + `state` enum('regular', 'vacant', 'temporary') NOT NULL DEFAULT 'vacant', + ); + } + + public function getTableName() + { + return '#__club_user_assocs'; + } + + public function getClassName() + { + return 'CommonClubsModelUserassoc'; + } + +} diff --git a/src/admin/common/models/offer.php b/src/admin/common/models/offer.php new file mode 100644 index 0000000..8040504 --- /dev/null +++ b/src/admin/common/models/offer.php @@ -0,0 +1,37 @@ +getValues()['name']; + } + + public function setName($name) + { + $this->setValue('name', $name); + } + + /*public function getClubs() + { + return $this->fetchAssociatedElements(new CommonClubsModelFactoryClub(), ''); + }*/ + + protected function prepareDelete($db) + { + $q = $db->getQuery(true); + $q->delete('#__club_offer_assocs'); + $q->where("offerid = {$this->getId()}"); + $db->setQuery($q); + $db->execute(); + } +} + diff --git a/src/admin/common/models/offerassoc.php b/src/admin/common/models/offerassoc.php new file mode 100644 index 0000000..066c1b9 --- /dev/null +++ b/src/admin/common/models/offerassoc.php @@ -0,0 +1,49 @@ +getValues()['club']; + } + + /** + * + * @param CommonClubsModelClub $club + */ + public function setClub($club) + { + $this->setValue('club', $club); + } + + /** + * + * @return CommonClubsModelOffer + */ + public function getOffer() + { + return $this->getValues()['offer']; + } + + /** + * + * @param CommonClubsModelOffer $offer + */ + public function setOffer($offer) + { + $this->setValue('offer', $offer); + } + +} diff --git a/src/admin/common/models/position.php b/src/admin/common/models/position.php new file mode 100644 index 0000000..a2e9133 --- /dev/null +++ b/src/admin/common/models/position.php @@ -0,0 +1,32 @@ +getValues()['name']; + } + + public function setName($name) + { + $this->setValue('name', $name); + } + + protected function prepareDelete($db) + { + $q = $db->getQuery(true); + $q->delete('#__club_user_assocs'); + $q->where("positionid = {$this->getId()}"); + $db->setQuery($q); + $db->execute(); + } + +} diff --git a/src/admin/common/models/user.php b/src/admin/common/models/user.php index ce8c821..62f880e 100644 --- a/src/admin/common/models/user.php +++ b/src/admin/common/models/user.php @@ -14,6 +14,158 @@ class CommonClubsModelUser extends AbstractCommonClubsModel { return $this->getValues()['name']; } - -} \ No newline at end of file + public function setName($name) + { + $this->setValue('name', $name); + } + + public function getUsername() + { + return $this->getValues()['user']; + } + + public function setUsername($user) + { + // XXX Check for validity + $this->setValue('user', $user); + } + + public function getAddress() + { + return $this->getValues()['address']; + } + + public function setAddress($address) + { + $this->setValue('address', $address); + } + + public function getCity() + { + return $this->getValues()['city']; + } + + public function setCity($city) + { + $this->setValue('city', $city); + } + + public function getMail() + { + return $this->getValues()['mail']; + } + + public function setMail($mail) { + $this->setValue('mail', $mail); + } + + public function getPhone() + { + return $this->getValues()['phone']; + } + + public function setPhone($phone) + { + $this->setValue('phone', $phone); + } + + public function getMobile() + { + return $this->getValues()['mobile']; + } + + public function setMobile($mobile) + { + $this->setValue('mobile', $mobile); + } + + public function isPasswordValid($password) + { + $hash = $this->getValues()['password']; + + if(password_verify($password, $hash)) + return true; + else + return false; + } + + public function checkRehashNeeded($newPassword, $check = false) + { + $hash = $this->getValues()['password']; + if(password_needs_rehash($hash, PASSWORD_DEFAULT)) + { + if($check) + { + if(! $this->isPasswordValid($newPassword)) + throw new Exception('Password did not match.'); + } + + $this->setPassword($newPassword); + return true; + } + else + return false; + } + + public function setPassword($password) + { + $hash = password_hash($password, PASSWORD_DEFAULT); + $this->setValue('password', $hash); + } + + public function getPositions() + { + return $this->fetchAssociatedElements(new CommonClubsModelFactoryUserassoc(), 'userid'); + } + + public function isPasswordSuitable($password) + { + if(strlen($password) < 8) + return false; + + $pwdLower = strtolower($password); + $userLower = strtolower($this->getName()); + + if(strpos($pwdLower, $userLower) || strpos($userLower, $pwdLower)) + return false; + + if( + preg_match('/.*[a-z].*/', $password) === false || + preg_match('/.*[A-Z].*/', $password) === false + ) + return false; + + return true; + } + + public function isUsernameSuitable($user) + { + $factory = new CommonClubsModelFactoryUser(); + $users = $factory->loadElements(null, null, function($q) use ($user){ + $q->where('main.user = ' . $q->q($user)); + }); + + if(sizeof($users) == 0) + return true; + elseif(sizeof($users) == 1) + { + if($this->getId() == $users[0]->getId()) + return true; + else + return false; + } + else + throw new Exception('The database is inconsistent!'); + } + + protected function prepareDelete($db) + { + $q = $db->getQuery(true); + $q->delete('#__club_user_assocs')->where("userid = {$this->getId()}"); + $db->setQuery($q); + $db->execute(); + } + +} + \ No newline at end of file diff --git a/src/admin/common/models/userassoc.php b/src/admin/common/models/userassoc.php new file mode 100644 index 0000000..ab1aa10 --- /dev/null +++ b/src/admin/common/models/userassoc.php @@ -0,0 +1,119 @@ +getValues()['user']; + } + + /** + * + * @return CommonClubsModelPosition + */ + public function getPosition() + { + return $this->getValues()['position']; + } + + /** + * + * @return CommonClubsModelClub + */ + public function getClub() + { + return $this->getValues()['club']; + } + + public function setUser($user) + { + $this->setValue('user', $user); + } + + public function setPosition($position) + { + $this->setValue('position', $position); + } + + public function setClub($club) + { + $this->setValue('club', $club); + } + + public function isAdmin() + { + return $this->getValues()['admin'] == 1; + } + + public function setAdmin($admin) + { + if($admin) + $this->setValue('admin', 1); + else + $this->setValue('admin', 0); + } + + public function getAddress() + { + return $this->getValues()['address']; + } + + public function setAddress($address) + { + $this->setValue('address', $address); + } + + public function getMail() + { + return $this->getValues()['mail']; + } + + public function setMail($mail) + { + $this->setValue('mail', $mail); + } + + public function getPhone() + { + return $this->getValues()['phone']; + } + + public function setPhone($phone) + { + $this->setValue('phone', $phone); + } + + public function getState() + { + return $this->getValues()['state']; + } + + public function setState($state) + { + switch(strtolower($state)) + { + case 'vacant': + case 'temporary': + case 'regular': + $state = strtolower($state); + break; + + default: + throw new Exception("Unknown state \"$state\" found."); + } + $this->setValue('state', $state); + } + +} From dd52d7ca31a70accb000447e7a80209344aa5be7 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 31 May 2019 14:30:52 +0200 Subject: [PATCH 22/41] Removed redundant code from ole implementation --- src/admin/abstract/model.php | 207 ------------ src/admin/abstract/modelfactory.php | 177 ----------- src/admin/clubs.php | 1 - src/admin/mymodels/club.php | 252 --------------- src/admin/mymodels/offer.php | 78 ----- src/admin/mymodels/offerassociation.php | 78 ----- .../mymodels/offerassociationfactory.php | 32 -- src/admin/mymodels/place.php | 113 ------- src/admin/mymodels/position.php | 66 ---- src/admin/mymodels/user.php | 298 ------------------ 10 files changed, 1302 deletions(-) delete mode 100644 src/admin/abstract/model.php delete mode 100644 src/admin/abstract/modelfactory.php delete mode 100644 src/admin/mymodels/club.php delete mode 100644 src/admin/mymodels/offer.php delete mode 100644 src/admin/mymodels/offerassociation.php delete mode 100644 src/admin/mymodels/offerassociationfactory.php delete mode 100644 src/admin/mymodels/place.php delete mode 100644 src/admin/mymodels/position.php delete mode 100644 src/admin/mymodels/user.php diff --git a/src/admin/abstract/model.php b/src/admin/abstract/model.php deleted file mode 100644 index 5ef288f..0000000 --- a/src/admin/abstract/model.php +++ /dev/null @@ -1,207 +0,0 @@ -id; - } - - protected abstract function getDataMappings(); - protected abstract function getTableName(); - - - protected function loadData(array $data) - { - $this->id = $data['id']; - - foreach($this->getDataMappings() as $m) - $this->$m = $data[$m]; - } - - protected static function loadElements(string $tableName, string $className, $where = null) - { - $dbo = Factory::getDbo(); - $q = $dbo->getQuery(true); - $q->select('*') - ->from($tableName); - - if(isset($where)) - { - $q->where($where); - } - - $dbo->setQuery($q); - $dbo->execute(); - $list = $dbo->loadAssocList(); - - $ret = array(); - foreach($list as $row) - { - $obj = new $className(); - $obj->loadData($row); - - $ret[] = $obj; - } - - return $ret; - } - - /** - * @param int $id - * @throws Exception - * @return ClubsOffer - */ - protected static function loadElement(int $id, string $tableName, string $className) - { - $dbo = Factory::getDbo(); - $q = $dbo->getQuery(true); - - $q->select('*')->from($tableName); - - $q->where('id=' . (int) $id); - $dbo->setQuery($q); - $dbo->execute(); - - $row = $dbo->loadAssoc(); - - if($row == null) - { - throw new Exception("No object of class $className found."); - // TODO - } - - $obj = new $className(); - $obj->loadData($row); - $obj->loadCustomData($row); - return $obj; - } - - protected function loadCustomData($assoc) - {} - - public function save() - { - if($this->id === 'new') - $this->insertElement(); - else - $this->updateElement(); - } - - private function insertElement() - { - if(! $this->isDataValid()) - throw new Exception('Data is invalid'); - - $dbo = Factory::getDbo(); - $q = $dbo->getQuery(true); - - $mappings = $this->getDataMappings(); - $values = $this->getDataValues($mappings, $q); - - $q->insert($this->getTableName()) - ->columns(array_map(array($q, 'qn'), $mappings)) - ->values(join(',', $values)) - ; - - $dbo->transactionStart(); - $dbo->setQuery($q); - $dbo->execute(); - $this->id = $dbo->insertid(); - $dbo->transactionCommit(); - } - - private function updateElement() - { - if(! $this->isDataValid()) - throw new Exception('Data is invalid'); - - $dbo = Factory::getDbo(); - $q = $dbo->getQuery(true); - - $mapping = $this->getDataMappings(); - $values = $this->getDataValues($mapping, $q); - - $q->update($this->getTableName()); - foreach($mapping as $m) - $q->set($q->qn($m) . '=' . $values[$m]); - $q->where("id=". (int) $this->id); - - $dbo->setQuery($q); - $dbo->execute(); - } - - private function getDataValues($mapping, $q) - { - $rawValues = array(); - - foreach($mapping as $m) - $rawValues[$m] = $this->$m; - - $this->filter($rawValues); - - $quotedValues = array(); - - foreach($mapping as $m) - { - if(is_bool($rawValues[$m])) - $quotedValues[$m] = $rawValues[$m] ? 'TRUE' : 'FALSE'; - else if(is_numeric($rawValues[$m])) - $quotedValues[$m] = $rawValues[$m]; - else - $quotedValues[$m] = $q->q($rawValues[$m]); - } - - $this->postQuoteFilter($quotedValues); - - return $quotedValues; - } - - protected function prepareDelete($dbo){} - - public function delete() - { - if($this->id === 'new') - return; - - $dbo = Factory::getDbo(); - $this->prepareDelete($dbo); - - $q = $dbo->getQuery(true); - $q->delete($this->getTableName()) - ->where('id=' . (int) $this->id); - - $dbo->setQuery($q); - $dbo->execute(); - } - - protected function getRequiredDataMappings() - { - return $this->getDataMappings(); - } - - protected function isDataValid() - { - foreach($this->getRequiredDataMappings() as $m) - { - if(!isset($this->$m) || is_null($this->$m) || $this->$m === null) - return false; - } - return true; - } - - protected function filter(&$values){} - protected function postQuoteFilter(&$values){} - - -} diff --git a/src/admin/abstract/modelfactory.php b/src/admin/abstract/modelfactory.php deleted file mode 100644 index f23cc98..0000000 --- a/src/admin/abstract/modelfactory.php +++ /dev/null @@ -1,177 +0,0 @@ -tableName = $tableName; - $this->className = $className; - } - - /** - * - * @param JDatabaseDriver $dbo - * @return array - */ - protected function getJoins($dbo) - { - /* - * Desired structure: - * array, ech element describing one join type. - * Each element of the array is a assosiated array describing the join: - * - Name of the table (must not be main) - * - Table alias - * - Type of the join (inner, left, right, outer), default is inner - * - Condition as a string (might need escaping) - * - Columns to be inserted in the join, defaults to * - * example: - * $ret = array(); - * $ret[] = array( - * 'name' => '#__table_name', - * 'alias' => 't1', - * 'type' => 'right', - * 'on' => 'main.extid = t1.id' - * 'select' => array('id','name') - * ); - * return $ret; - */ - return array(); - } - - /** - * - * @param JDatabaseDriver $dbo - * @param JDatabaseQuery $q - */ - private function insertJoinRelations($dbo, $q) - { - $joins = $this->getJoins($dbo); - foreach($joins as $j) - { - if(is_null($j['name'])) - throw new Exception('No name was given in the join.'); - if(is_null($j['alias'])) - throw new Exception('No alisas was given in the join.'); - - $jstr = $dbo->qn($j['name'], $j['alias']); - - if(is_null($j['on'])) - throw new Exception('No on clause was provided.'); - - $jstr .= " ON {$j['on']}"; - - if(is_null($j['type'])) - $j['type'] = 'inner'; - - switch($j['type']) - { - case 'inner': - $q->innerJoin($jstr); - break; - - case 'outer': - $q->outerJoin($jstr); - break; - - case 'left': - $q->leftJoin($jstr); - break; - - case 'right': - $q->rightJoin($jstr); - break; - - default: - throw new Exception("Type of join unknown: {$j['type']}."); - } - - $this->addJoinSelectColumns($j, $q); - } - } - - /** - * - * @param array $j - * @param JDatabaseQuery $q - */ - private function addJoinSelectColumns($j, $q) - { - // TODO Quote names - if(is_null($j['select'])) - $j['select'] = '*'; - - if(is_array($j['select'])) - { - array_map(function(&$str) use ($j) { - $str = "{$j['alias']}.$str"; - }, $j['select']); - } - else - { - $q->select("{$j['alias']}.{$j['select']}"); - } - } - - public function loadElement(int $id) - { - $condition = "id = $id"; - - return $this->loadFirstElement($condition); - } - - public function loadFirstElement($condition) - { - $objs = $this->loadElements($condition); - - if(sizeof($objs) == 0) - { - throw new Exception("No object of class {$this->className} found."); - // TODO - } - - return $objs[0]; - } - - public function loadElements($condition = null) - { - $dbo = Factory::getDbo(); - $q = $dbo->getQuery(true); - - $q->select('main.*')->from($this->tableName, 'main'); - $this->insertJoinRelations($dbo, $q); - - if(isset($condition)) - { - $q->where($condition); - } - $dbo->setQuery($q); - $dbo->execute(); - - $list = $dbo->loadAssocList(); - - $ret = array(); - foreach($list as $row) - { - $ret[] = $this->createElement($row); - } - - return $ret; - } - - protected function createElement($row) - { - $obj = new $this->className(); - $obj->loadData($row); - $obj->loadCustomData($row); - return $obj; - } -} diff --git a/src/admin/clubs.php b/src/admin/clubs.php index f1320ba..debe58e 100644 --- a/src/admin/clubs.php +++ b/src/admin/clubs.php @@ -9,7 +9,6 @@ defined('_JEXEC') or die; JLoader::discover('Clubs', JPATH_ROOT . '/administrator/components/com_clubs/mymodels'); JLoader::registerPrefix('AbstractClubs', JPATH_ROOT . '/administrator/components/com_clubs/abstract'); JLoader::registerPrefix('AbstractCommonClubs', JPATH_ROOT . '/administrator/components/com_clubs/common/abstract'); -// JLoader::registerPrefix('ClubsHelper', JPATH_ROOT . '/administrator/components/com_clubs/common/helper'); JLoader::registerPrefix('CommonClubsModel', JPATH_ROOT . '/administrator/components/com_clubs/common/models'); $controller = BaseController::getInstance("Clubs"); diff --git a/src/admin/mymodels/club.php b/src/admin/mymodels/club.php deleted file mode 100644 index 7a5c464..0000000 --- a/src/admin/mymodels/club.php +++ /dev/null @@ -1,252 +0,0 @@ -address; - } - - /** - * @return string - */ - public function getCity() - { - return $this->city; - } - - /** - * @return string - */ - public function getHomepage() - { - return $this->homepage; - } - - /** - * @return string - */ - public function getMail() - { - return $this->mail; - } - - /** - * @return string - */ - public function getIban() - { - return $this->iban; - } - - /** - * @return string - */ - public function getBic() - { - return $this->bic; - } - - /** - * @return bool - */ - public function isCharitable() - { - return $this->charitable; - } - - /** - * @return int - */ - public function getPresidentId() - { - return $this->president->getId(); - } - - /** - * @return ClubsUser - */ - public function getPresident() - { - return $this->president; - } - - /** - * @param string $address - */ - public function setAddress( $address) - { - $this->address = $address; - } - - /** - * @param string $city - */ - public function setCity($city) - { - $this->city = $city; - } - - /** - * @param string $homapge - */ - public function setHomepage($homapge) - { - $this->homepage = $homapge; - } - - /** - * @param string $mail - */ - public function setMail($mail) - { - $this->mail = $mail; - } - - /** - * @param string $iban - */ - public function setIban($iban) - { - $this->iban = $iban; - } - - /** - * @param string $bic - */ - public function setBic($bic) - { - $this->bic = $bic; - } - - /** - * @param bool $charitable - */ - public function setCharitable(bool $charitable) - { - $this->charitable = $charitable; - } - - /** - * @param int $presidentId - */ - public function setPresidentId(int $presidentId) - { - $this->president = ClubsUser::loadUser($presidentId); - } - - /** - * @param ClubsUser $user - */ - public function setPresident(ClubsUser $user) - { - $this->president = $user; - } - - /** - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * @param string $name - */ - public function setName($name) - { - $this->name = $name; - } - - - - public static function loadClubs() - { - return self::loadElements(self::tableName, self::className); - } - - public static function loadClub($id) - { - return self::loadElement($id, self::tableName, self::className); - } - - protected function __construct() - {} - - public static function createClub() - { - $club = new ClubsClub(); - $club->id = 'new'; - return $club; - } - - - - - - public function getPlaces() - { - return ClubsPlace::loadPlacesOfClub($this->id); - } - - - public function getOffers() - { - return 0; - } - - - protected function loadCustomData($assoc) - { - parent::loadCustomData($assoc); - $this->president = ClubsUser::loadUser($assoc['president']); - } - - protected function postQuoteFilter(&$values) - { - parent::postQuoteFilter($values); - $values['president'] = $this->president->getId(); - } - - protected function prepareDelete($dbo) - {} - - protected function getDataMappings() - { - return array('name', 'address', 'city', 'homepage', 'mail', 'iban', 'bic', 'charitable', 'president'); - } - - protected function getRequiredDataMappings() - { - return array('name', 'address', 'city', 'mail', 'iban', 'bic'); - } - - private const tableName = '#__club_clubs'; - private const className = 'ClubsClub'; - protected function getTableName() - { - return self::tableName; - } - - - -} diff --git a/src/admin/mymodels/offer.php b/src/admin/mymodels/offer.php deleted file mode 100644 index b3d1f1c..0000000 --- a/src/admin/mymodels/offer.php +++ /dev/null @@ -1,78 +0,0 @@ -name; - } - - /** - * @param string $name - */ - public function setName($name) - { - $this->name = $name; - } - - protected function __construct() - {} - - private const tableName = '#__club_offers'; - private const className = 'ClubsOffer'; - - public static function getFactory() - { - return new class extends AbstractClubsModelFactory { - public function __construct() - { - parent::__construct($this->tableName, $this->className); - } - - protected function getJoins($dbo) - { - $ret = array(); - return $ret; - } - - }; - } - - public static function loadOffers() - { - return self::loadElements(self::tableName, self::className); - } - - - public static function loadOffer(int $id) - { - return self::loadElement($id, self::tableName, self::className); - } - - public static function createOffer() - { - $offer = new ClubsOffer(); - $offer->id = 'new'; - return $offer; - } - - protected function getDataMappings() - { - return array('name'); - } - - protected function getTableName() - { - return self::tableName; - } - -} diff --git a/src/admin/mymodels/offerassociation.php b/src/admin/mymodels/offerassociation.php deleted file mode 100644 index 2329066..0000000 --- a/src/admin/mymodels/offerassociation.php +++ /dev/null @@ -1,78 +0,0 @@ -offerid); - } - - public function getClub() - { - return ClubsClub::loadClub($this->clubid); - } - - public function setOffer($offer) - { - $this->offerid = $offer->getId(); - } - - public function getName() - { - return $this->getOffer()->getName(); - } - - public function setClub($club) - { - $this->clubid = $club->getId(); - } - - protected function __construct() - {} - - public static function getFactory() - { - return new ClubsOfferAssociationFactory(); - } - - private const tableName = '#__club_offer_assocs'; - private const className = 'ClubsOfferAssociation'; - - public static function loadOfferAssociationsOfClub($club) - { - $cid = $club->getId(); - return self::loadElements(self::tableName, self::className, "clubid = $cid"); - // FIXME Use join to make access faster - } - - - public static function loadOfferAssociation(int $id) - { - return self::loadElement($id, self::tableName, self::className); - } - - public static function createOfferAssociation() - { - $offer = new ClubsOfferAssociation(); -// $offer->id = 'new'; - return $offer; - } - - protected function getDataMappings() - { -// return array('name'); - } - - protected function getTableName() - { - return self::tableName; - } - -} diff --git a/src/admin/mymodels/offerassociationfactory.php b/src/admin/mymodels/offerassociationfactory.php deleted file mode 100644 index 5853657..0000000 --- a/src/admin/mymodels/offerassociationfactory.php +++ /dev/null @@ -1,32 +0,0 @@ - '#__club_offers', - 'alias' => 'offer', - 'on' => 'main.offerid = offer.id', - 'select' => 'id as offerId' - ); - $ret[] = array( - 'name' => '#__club_clubs', - 'alias' => 'club', - 'on' => 'main.clubid = club.id', - 'select' => 'id AS clubId' - ); - return $ret; - } - -} diff --git a/src/admin/mymodels/place.php b/src/admin/mymodels/place.php deleted file mode 100644 index c1b7fcd..0000000 --- a/src/admin/mymodels/place.php +++ /dev/null @@ -1,113 +0,0 @@ -name; - } - - /** - * @param string $name - */ - public function setName(string $name) - { - $this->name = $name; - } - - /** - * @return string - */ - public function getAddress() - { - return $this->address; - } - - /** - * @return string - */ - public function getArea() - { - return $this->area; - } - - /** - * @param string $address - */ - public function setAddress(string $address) - { - $this->address = $address; - } - - /** - * @param string $area - */ - public function setArea(string $area) - { - $this->area = $area; - } - - - public function getClub() - { - return ClubsClub::loadClub($this->clubid); - } - - public function setClub($club) - { - $this->clubid = $club->getId(); - } - - - protected function __construct() - {} - - public static function loadPlaces() - { - return self::loadElements(self::tableName, self::className); - } - - public static function loadPlacesOfClub($clubId) - { - return self::loadElements(self::tableName, self::className, "clubid = $clubId"); - } - - public static function loadPlace(int $id) - { - return self::loadElement($id, self::tableName, self::className); - } - - public static function createPlace() - { - $place = new ClubsPlace(); - $place->id = 'new'; - return $place; - } - - protected function getDataMappings() - { - return array('name', 'address', 'area', 'clubid'); - } - - private const tableName = '#__club_places'; - private const className = 'ClubsPlace'; - - protected function getTableName() - { - return self::tableName; - } - - -} diff --git a/src/admin/mymodels/position.php b/src/admin/mymodels/position.php deleted file mode 100644 index 79d9a39..0000000 --- a/src/admin/mymodels/position.php +++ /dev/null @@ -1,66 +0,0 @@ -name; - } - - /** - * @param string $name - */ - public function setName(string $name) - { - $this->name = $name; - } - - protected function __construct() - {} - - private const tableName = '#__club_positions'; - private const className = 'ClubsPosition'; - - public static function loadPositions() - { - return self::loadElements(self::tableName, self::className); - } - -// public static function loadPositions($clubId) -// { -// return self::loadElements(self::tableName, self::className, "clubid = $clubId"); -// } - - public static function loadPosition(int $id) - { - return self::loadElement($id, self::tableName, self::className); - } - - public static function createPosition() - { - $position = new ClubsPosition(); - $position->id = 'new'; - return $position; - } - - protected function getDataMappings() - { - return array('name'); - } - - protected function getTableName() - { - return self::tableName; - } - - - -} diff --git a/src/admin/mymodels/user.php b/src/admin/mymodels/user.php deleted file mode 100644 index 892a772..0000000 --- a/src/admin/mymodels/user.php +++ /dev/null @@ -1,298 +0,0 @@ -mail; - } - - /** - * @param string $mail - */ - public function setMail($mail) - { - $this->mail = $mail; - } - - /** - * @return string - */ - public function getUser() - { - return $this->user; - } - - /** - * @return string - */ - public function getHash() - { - return $this->password; - } - - /** - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * @return string - */ - public function getAddress() - { - return $this->address; - } - - /** - * @return string - */ - public function getCity() - { - return $this->city; - } - - /** - * @return string - */ - public function getPhone() - { - return $this->phone; - } - - /** - * @return string - */ - public function getMobile() - { - return $this->mobile; - } - - /** - * @param string $user - */ - public function setUser($user, bool $force = false) - { - if($this->id === 'new') - $valid = self::isUserNameFree($user); - else - $valid = self::isUserNameFree($user, $this->id); - - if(!$force && ! $valid) - throw new UserInvalidException(); - - $this->user = $user; - } - - /** - * @param string $hash - */ - public function setPassword(string $password) - { - if(! $this->checkPasswordStrength($password)) - throw new PasswordInvalidException(); - - $this->password = password_hash($password, PASSWORD_DEFAULT); - } - - public function isPasswordValid(string $password) - { - $valid = password_verify($password, $this->password); - - if($valid) - { - $this->checkForRehashing($password); - } - - return $valid; - } - - /** - * @param string $name - */ - public function setName($name) - { - $this->name = $name; - } - - /** - * @param string $address - */ - public function setAddress($address) - { - $this->address = $address; - } - - /** - * @param string $city - */ - public function setCity($city) - { - $this->city = $city; - } - - /** - * @param string $phone - */ - public function setPhone($phone) - { - $this->phone = $phone; - } - - /** - * @param string $mobile - */ - public function setMobile($mobile) - { - $this->mobile = $mobile; - } - - protected function __construct() - {} - - private const tableName = '#__club_users'; - private const className = 'ClubsUser'; - - public static function loadUsers() - { - return self::loadElements(self::tableName, self::className); - } - - public static function loadUser(int $id) - { - return self::loadElement($id, self::tableName, self::className); - } - - public static function createUser() - { - $user = new ClubsUser(); - $user->id = 'new'; - return $user; - } - - private function updateUser() - { - $dbo = Factory::getDbo(); - $q = $dbo->getQuery(true); - - $vuser = $q->q($this->user); - $vpassword = $q->q($this->password); - $vname = $q->q($this->name); - $vaddress = $q->q($this->address); - $vcity = $q->q($this->city); - $vmail = $q->q($this->mail); - $vphone = empty($this->phone) ? 'NULL' : $q->q($this->phone); - $vmobile = empty($this->mobile) ? 'NULL' : $q->q($this->mobile); - // FIXME Check null vlaues - $q->update('#__club_users') - ->set(array( - "user=$vuser", - "password=$vpassword", - "name=$vname", - "address = $vaddress", - "city=$vcity", - "mail=$vmail", - "phone=$vphone", - "mobile=$vmobile" - )) - ->where("id=". (int) $this->id) - ; - - $dbo->setQuery($q); - $dbo->execute(); - } - - private function checkForRehashing(string $password) - { - if($this->id === 'new') - return; - - if(password_needs_rehash($this->password, PASSWORD_DEFAULT) || true) - { - $copy = ClubsUser::loadUser($this->id); - $copy->password = password_hash($password, PASSWORD_DEFAULT); - $copy->save(); - -// $this->password = password_hash($password, PASSWORD_DEFAULT); - -// $dbo = Factory::getDbo(); - -// $q = $dbo->getQuery(true); -// $q->update(self::tableName)->set('password=' . $q->q($this->password))->where('id=' . (int) $this->id); -// $dbo->setQuery($q); -// $dbo->execute(); - } - } - - public static function isUserNameFree($username, int $id = -1) - { - $db = Factory::getDbo(); - $q = $db->getQuery(true); - $q->select('COUNT(*)')->from(self::tableName) - ->where('id <> ' . (int) $id) - ->where('user = ' . $q->q($username)); - $db->setQuery($q); - $db->execute(); - $row = $db->loadRow(); - return $row[0] == 0; - } - - public static function checkPasswordStrength($pwd) - { - if(strlen($pwd) < 6) - return false; - - if(preg_match_all('/[A-Z]/', $pwd) === false) - return false; - - if(preg_match_all('/[a-z]/', $pwd) === false) - return false; - - if(preg_match_all('/[0-9]/', $pwd) === false) - return false; - - return true; - } - - protected function getDataMappings() - { - return array('user', 'password', 'name', 'address', 'city', 'mail', 'phone', 'mobile'); - } - - protected function getRequiredDataMappings() - { - return array('user', 'password', 'name', 'address', 'city', 'mail'); - } - - protected function getTableName() - { - return self::tableName; - } - -} From 904d31843a9b03b24e53eeb4851322ca4fd4451e Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Mon, 3 Jun 2019 11:35:07 +0200 Subject: [PATCH 23/41] Abstract list working in general, tested with single example --- src/admin/abstract/view/list.php | 67 +++++++++++++++++++++ src/admin/abstract/view/single.php | 78 +++++++++++++++---------- src/admin/common/abstract/model.php | 9 ++- src/admin/views/offers/tmpl/default.php | 10 ++-- src/admin/views/offers/view.html.php | 17 ++++-- 5 files changed, 138 insertions(+), 43 deletions(-) create mode 100644 src/admin/abstract/view/list.php diff --git a/src/admin/abstract/view/list.php b/src/admin/abstract/view/list.php new file mode 100644 index 0000000..d794306 --- /dev/null +++ b/src/admin/abstract/view/list.php @@ -0,0 +1,67 @@ +prepared = TRUE; + + $this->objects = $this->loadObjects(); + $baseUrl = "index.php?option=com_clubs"; + $viewName = $this->getSingleViewName(); + $controllerName = $this->getControllerName(); + + $this->addUrl = Route::_("$baseUrl&view=$viewName&id=new"); + $this->changeUrl = Route::_("$baseUrl&view=$viewName&id=__ID__"); + $this->delUrl = Route::_("$baseUrl&task=$controllerName.delete&id=__ID__"); + } + + public function display($tpl = null) + { + if(!$this->prepared) + $this->prepareDisplay(); + + parent::display($tpl); + } + + protected abstract function getSingleBaseName(); + + protected function getControllerName() + { + return $this->getSingleBaseName(); + } + + protected function getSingleViewName() + { + return $this->getSingleBaseName(); + } + + /** + * @return AbstractCommonClubsModelFactory + */ + protected abstract function getFactory(); + + protected function loadObjects() + { + $factory = $this->getFactory(); + return $factory->loadElements(); + } + +} diff --git a/src/admin/abstract/view/single.php b/src/admin/abstract/view/single.php index 70d1a6a..8afa862 100644 --- a/src/admin/abstract/view/single.php +++ b/src/admin/abstract/view/single.php @@ -11,6 +11,9 @@ abstract class AbstractClubsViewSingle extends HtmlView { protected $address; + /** + * @var AbstractCommonClubsModel + */ protected $object; protected $isNew; @@ -23,32 +26,28 @@ abstract class AbstractClubsViewSingle extends HtmlView $input = Factory::getApplication()->input; $id = $input->get->get('id'); - $modelClass = 'Clubs' . $this->getModelName(); + $controllerName = $this->getControllerName(); - $controller = $this->getControllerName(); if($id === 'new') { - $this->address = Route::_("index.php?option=com_clubs&task={$controller}.new"); - $this->object = call_user_func(array($modelClass, 'create' . $this->getModelName())); + $this->address = Route::_("index.php?option=com_clubs&task={$controllerName}.new"); + $this->object = $this->createNewObject(); $this->isNew = true; } else if(is_numeric($id)) { - $this->address = Route::_("index.php?option=com_clubs&task={$controller}.change"); - $this->object = call_user_func(array($modelClass, 'load' . $this->getModelName()), (int) $id); + $id = (int) $id; + $this->address = Route::_("index.php?option=com_clubs&task={$controllerName}.change&id=$id"); + $this->object = $this->loadObject($id); $this->isNew = false; } else - throw new Exception('Need an object id.'); - - if($input->get->get('data', null, 'json') != null) + throw new Exception('Need a valid object id.'); + + $jsonData = $input->get->get('data', null, 'json'); + if($jsonData !== null) { - // Restore previous data - $dataurl = $input->get->get('data', null, 'json'); - $data = json_decode($dataurl, true); - - $controller = $this->getElementController(); - $controller->applyData($this->object, $data); + $this->object->unpack($jsonData); } } @@ -61,26 +60,45 @@ abstract class AbstractClubsViewSingle extends HtmlView parent::display($tpl); } - protected abstract function getViewName(); +// protected abstract function getViewName(); - protected function getControllerName() + protected abstract function getControllerName(); + +// protected function getModelName() +// { +// $name = $this->getViewName(); +// return $this->capitalize($name); +// } + +// protected function getModelClass() +// { +// return 'Clubs' . $this->getModelName(); +// } + +// private function capitalize($s) +// { +// $first = substr($s, 0, 1); +// $rest = substr($s, 1); +// return strtoupper($first) . $rest; +// } + +// protected abstract function getElementController(); + + /** + * @return AbstractCommonClubsModelFactory + */ + protected abstract function getFactory(); + + protected function createNewObject() { - return $this->getViewName(); + $factory = $this->getFactory(); + return $factory->createNew(); } - protected function getModelName() + protected function loadObject($id) { - $name = $this->getViewName(); - return $this->capitalize($name); + $factory = $this->getFactory(); + return $factory->loadById($id); } - private function capitalize($s) - { - $first = substr($s, 0, 1); - $rest = substr($s, 1); - return strtoupper($first) . $rest; - } - - protected abstract function getElementController(); - } diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 90babdc..65b4fac 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -327,12 +327,15 @@ abstract class AbstractCommonClubsModel $vals = $this->filterPackData($vals); $json = json_encode($vals); - return urldecode($json); + return urlencode($json); } - public function unpack($str) + public function unpack($str, $decode = false) { - $json = urlencode($str); + if($decode) + $json = urldecode($str); + else + $json = $str; $data = json_decode($json, true); $vals = $this->unpackExternalReferencesFromKeys($data); diff --git a/src/admin/views/offers/tmpl/default.php b/src/admin/views/offers/tmpl/default.php index 433af8d..470fbd5 100644 --- a/src/admin/views/offers/tmpl/default.php +++ b/src/admin/views/offers/tmpl/default.php @@ -11,18 +11,18 @@ defined('_JEXEC') or die; Bezeichnung - Löschen? + id -offers as $offer): ?> +objects as $offer): ?> getId()); ?> - getName()); ?> - getId()); ?>'>Del + getId(), $this->changeUrl); ?>'>getName()); ?> + getId(), $this->delUrl); ?>'>Löschen getId()); ?> - + diff --git a/src/admin/views/offers/view.html.php b/src/admin/views/offers/view.html.php index 36b7fbb..dff78b7 100644 --- a/src/admin/views/offers/view.html.php +++ b/src/admin/views/offers/view.html.php @@ -1,20 +1,27 @@ offers = ClubsOffer::loadOffers(); - - ToolbarHelper::title('Club-Management - Angebote'); + ToolbarHelper::title('Club-Management - Angebote', 'list'); parent::display($tpl); } + protected function getFactory() + { + return new CommonClubsModelFactoryOffer(); + } + + protected function getSingleBaseName() + { + return 'offer'; + } + } From eb704af915e3d188b7f195b3bebda37deb06a836 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Mon, 3 Jun 2019 11:59:36 +0200 Subject: [PATCH 24/41] List views updated to new structure --- src/admin/common/models/position.php | 2 +- src/admin/views/clubs/tmpl/default.php | 18 ++++++++---------- src/admin/views/clubs/view.html.php | 19 ++++++++++++------- src/admin/views/positions/tmpl/default.php | 12 +++++------- src/admin/views/positions/view.html.php | 17 ++++++++++++----- src/admin/views/users/tmpl/default.php | 12 ++++++------ src/admin/views/users/view.html.php | 17 +++++++++++------ 7 files changed, 55 insertions(+), 42 deletions(-) diff --git a/src/admin/common/models/position.php b/src/admin/common/models/position.php index a2e9133..0c836ae 100644 --- a/src/admin/common/models/position.php +++ b/src/admin/common/models/position.php @@ -7,7 +7,7 @@ class CommonClubsModelPosition extends AbstractCommonClubsModel { protected function getFactory() { - new CommonClubsModelFactoryPosition(); + return new CommonClubsModelFactoryPosition(); } public function getName() diff --git a/src/admin/views/clubs/tmpl/default.php b/src/admin/views/clubs/tmpl/default.php index 33c30ea..0dbab60 100644 --- a/src/admin/views/clubs/tmpl/default.php +++ b/src/admin/views/clubs/tmpl/default.php @@ -1,7 +1,6 @@ Bezeichnung - Stadt + Stadt Homepage - E-Mail - Löschen? - id + E-Mail + + id -clubs as $club): ?> -getId()); ?> +objects as $club): ?> getHomepage()); ?> getMail()); ?> - getName()); ?> + getId(), $this->changeUrl); ?>'>getName()); ?> getCity()); ?> - + getId(), $this->delUrl); ?>'>Löschen getId()); ?> - + diff --git a/src/admin/views/clubs/view.html.php b/src/admin/views/clubs/view.html.php index 9124741..91f0630 100644 --- a/src/admin/views/clubs/view.html.php +++ b/src/admin/views/clubs/view.html.php @@ -1,22 +1,27 @@ offers = ClubsOffer::loadOffers(); - $factory = new CommonClubsModelFactoryClub(); - $this->clubs = $factory->loadElements(); - - ToolbarHelper::title('Club-Management - Clubs'); + ToolbarHelper::title('Club-Management - Clubs', 'cube'); parent::display($tpl); } + protected function getFactory() + { + return new CommonClubsModelFactoryClub(); + } + + protected function getSingleBaseName() + { + return 'club'; + } + } diff --git a/src/admin/views/positions/tmpl/default.php b/src/admin/views/positions/tmpl/default.php index ff8a4fa..6fbb9b3 100644 --- a/src/admin/views/positions/tmpl/default.php +++ b/src/admin/views/positions/tmpl/default.php @@ -1,7 +1,6 @@ Bezeichnung - Löschen? + id -positions as $position): ?> -getId()); ?> +objects as $position): ?> - getName()); ?> - getId()); ?>'>Del + getId(), $this->changeUrl); ?>'>getName()); ?> + getId(), $this->delUrl); ?>'>Löschen getId()); ?> - + diff --git a/src/admin/views/positions/view.html.php b/src/admin/views/positions/view.html.php index d165d50..dd7d94c 100644 --- a/src/admin/views/positions/view.html.php +++ b/src/admin/views/positions/view.html.php @@ -1,20 +1,27 @@ positions = ClubsPosition::loadPositions(); - - ToolbarHelper::title('Club-Management - Positionen'); + ToolbarHelper::title('Club-Management - Positionen', 'users'); parent::display($tpl); } + protected function getFactory() + { + return new CommonClubsModelFactoryPosition(); + } + + protected function getSingleBaseName() + { + return 'position'; + } + } diff --git a/src/admin/views/users/tmpl/default.php b/src/admin/views/users/tmpl/default.php index fd1c66f..d03b8eb 100644 --- a/src/admin/views/users/tmpl/default.php +++ b/src/admin/views/users/tmpl/default.php @@ -14,22 +14,22 @@ defined('_JEXEC') or die; Name Ort E-Mail - Löschen? + id -users as $user): ?> -getId()); ?> +objects as $user): ?> +getId(), $this->changeUrl); ?> - getUser()); ?> + getUserName()); ?> getName()); ?> getCity()); ?> getMail()); ?> - getId()); ?>'>Del + getId(), $this->delUrl); ?>'>Löschen getId()); ?> - + diff --git a/src/admin/views/users/view.html.php b/src/admin/views/users/view.html.php index 2b9ce88..6389d88 100644 --- a/src/admin/views/users/view.html.php +++ b/src/admin/views/users/view.html.php @@ -1,22 +1,27 @@ users = ClubsUser::loadUsers(); - ToolbarHelper::title('Club-Management - Personen', 'user'); parent::display($tpl); } + protected function getFactory() + { + return new CommonClubsModelFactoryUser(); + } + + protected function getSingleBaseName() + { + return 'user'; + } + } From 65ccfe23f7b87024d35923c1969fb287b9cf3493 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Mon, 3 Jun 2019 14:39:09 +0200 Subject: [PATCH 25/41] Corrected all views to the structure --- src/admin/common/abstract/model.php | 2 +- src/admin/common/models/club.php | 2 +- .../common/models/factory/offerassoc.php | 2 +- src/admin/common/models/offerassoc.php | 2 +- src/admin/views/club/tmpl/default.php | 49 +++++++++++++---- src/admin/views/club/view.html.php | 25 +++------ src/admin/views/offer/view.html.php | 15 +++--- src/admin/views/position/view.html.php | 17 +++--- src/admin/views/user/tmpl/default.php | 18 +++---- src/admin/views/user/view.html.php | 52 +++++-------------- 10 files changed, 89 insertions(+), 95 deletions(-) diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 65b4fac..bdb0224 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -306,7 +306,7 @@ abstract class AbstractCommonClubsModel return $factory->loadElements($allConstraints, $sorting); } else - $factory->loadElements($condition, $sorting); + return $factory->loadElements($condition, $sorting); } protected function filterPackData($values) diff --git a/src/admin/common/models/club.php b/src/admin/common/models/club.php index 94eea67..a41e52b 100644 --- a/src/admin/common/models/club.php +++ b/src/admin/common/models/club.php @@ -63,7 +63,7 @@ class CommonClubsModelClub extends AbstractCommonClubsModel public function getOffers() { - $assocs = $this->fetchAssociatedElements(new CommonClubsModelFactoryOfferassocs(), 'clubid'); + $assocs = $this->fetchAssociatedElements(new CommonClubsModelFactoryOfferassoc(), 'clubid'); $offersFactory = new CommonClubsModelFactoryOffer(); $allOffers = $offersFactory->loadElements(); diff --git a/src/admin/common/models/factory/offerassoc.php b/src/admin/common/models/factory/offerassoc.php index a17d6e8..14688b0 100644 --- a/src/admin/common/models/factory/offerassoc.php +++ b/src/admin/common/models/factory/offerassoc.php @@ -3,7 +3,7 @@ // No direct access. defined('_JEXEC') or die; -class CommonClubsModelFactoryOfferassocs extends AbstractCommonClubsModelFactory +class CommonClubsModelFactoryOfferassoc extends AbstractCommonClubsModelFactory { protected function fetchAttributes() { diff --git a/src/admin/common/models/offerassoc.php b/src/admin/common/models/offerassoc.php index 066c1b9..5f59cd8 100644 --- a/src/admin/common/models/offerassoc.php +++ b/src/admin/common/models/offerassoc.php @@ -7,7 +7,7 @@ class CommonClubsModelOfferassoc extends AbstractCommonClubsModel { protected function getFactory() { - return new CommonClubsModelFactoryOfferassocs(); + return new CommonClubsModelFactoryOfferassoc(); } /** diff --git a/src/admin/views/club/tmpl/default.php b/src/admin/views/club/tmpl/default.php index 59167e6..e66f5b7 100644 --- a/src/admin/views/club/tmpl/default.php +++ b/src/admin/views/club/tmpl/default.php @@ -9,6 +9,7 @@ defined('_JEXEC') or die;
+

Stammdaten

@@ -66,15 +67,6 @@ defined('_JEXEC') or die; isNew): ?> - - - - @@ -82,6 +74,45 @@ defined('_JEXEC') or die;
Bezeichnung
Angebote - offers as $o): ?> - getId(); ?>' > - getName()); ?>
- -
ID object->getId(); ?>
+ +isNew): ?> +

Angebote

+object->getOffers() as $oconf): ?> + getId(); ?>' > + getName()); ?>
+ +

Posten

+ + + + + + + + + + object->getUsers() as $ua): ?> + + + + + + + + + +
RolleNameStadtAdmin?ID
getPosition()->getName()); ?>getUser()->getName()); ?>getUser()->getCity()); ?>isAdmin()) echo ""; ?> + + + getId(); ?>
+

Neuen Posten einfügen

+ +
'>Zurück zur Übersicht
+
+
+
Ein Test
+
+ diff --git a/src/admin/views/club/view.html.php b/src/admin/views/club/view.html.php index 9b0b323..77934d5 100644 --- a/src/admin/views/club/view.html.php +++ b/src/admin/views/club/view.html.php @@ -11,39 +11,26 @@ class ClubsViewClub extends AbstractClubsViewSingle { function display($tpl = null) { - ToolbarHelper::title('Club-Management - Verein'); + ToolbarHelper::title('Club-Management - Verein', 'cube'); $this->prepareDisplay(); $userFactory = new CommonClubsModelFactoryUser(); $this->users = $userFactory->loadElements(); - if($this->isNew) - { - - } - else - { -// $offers = ClubsOffer::loadOffers(); -// $currentOffers = $this->object->getOffers(); - -// $this->offers = array_map(function($offer) use ($currentOffers){ -// $mark = False; -// return array('offer'=>$offer, 'mark'=>$mark); -// }, $offers); - } - parent::display($tpl); } - protected function getViewName() + protected function getControllerName() { return 'club'; } - protected function getElementController() + + protected function getFactory() { - return new ClubsControllerClub(); + return new CommonClubsModelFactoryClub(); } + } diff --git a/src/admin/views/offer/view.html.php b/src/admin/views/offer/view.html.php index aa35699..e1a2bad 100644 --- a/src/admin/views/offer/view.html.php +++ b/src/admin/views/offer/view.html.php @@ -11,19 +11,20 @@ class ClubsViewOffer extends AbstractClubsViewSingle { function display($tpl = null) { - - ToolbarHelper::title('Club-Management - Angebot'); + ToolbarHelper::title('Club-Management - Angebot', 'file'); parent::display($tpl); } - protected function getViewName() + protected function getFactory() + { + return new CommonClubsModelFactoryOffer(); + } + protected function getControllerName() { return 'offer'; } - protected function getElementController() - { - return new ClubsControllerOffer(); - } + + } diff --git a/src/admin/views/position/view.html.php b/src/admin/views/position/view.html.php index bfd13a0..150fedc 100644 --- a/src/admin/views/position/view.html.php +++ b/src/admin/views/position/view.html.php @@ -9,19 +9,20 @@ JLoader::register("ClubsControllerPosition", JPATH_ROOT . "/administrator/compon class ClubsViewPosition extends AbstractClubsViewSingle { - protected function getViewName() + + function display($tpl = null) + { + ToolbarHelper::title('Club-Management - Position', 'users'); + parent::display($tpl); + } + protected function getControllerName() { return 'position'; } - protected function getElementController() + protected function getFactory() { - return new ClubsControllerPosition(); + return new CommonClubsModelFactoryPosition(); } - function display($tpl = null) - { - ToolbarHelper::title('Club-Management - Position'); - parent::display($tpl); - } } diff --git a/src/admin/views/user/tmpl/default.php b/src/admin/views/user/tmpl/default.php index cf6632b..13467ec 100644 --- a/src/admin/views/user/tmpl/default.php +++ b/src/admin/views/user/tmpl/default.php @@ -8,11 +8,11 @@ defined('_JEXEC') or die; ?>
- + - + @@ -24,33 +24,33 @@ defined('_JEXEC') or die; - + - + - + - + - + isNew): ?> - +
Username
Passwort
BĂĽrgerlicher Name
Adresse - +
Stadt
E-Mail
Telefon
Handy
IDuser->getId(); ?>object->getId(); ?>
diff --git a/src/admin/views/user/view.html.php b/src/admin/views/user/view.html.php index 1fc6cd1..553b2e7 100644 --- a/src/admin/views/user/view.html.php +++ b/src/admin/views/user/view.html.php @@ -1,54 +1,28 @@ input; - $id = $input->get->get('id'); - - if($id === 'new') - { - $this->address = Route::_('index.php?option=com_clubs&task=user.new'); - $this->user = ClubsUser::createUser(); - $this->isNew = true; - } - else if(is_numeric($id)) - { - $this->address = Route::_('index.php?option=com_clubs&task=user.change'); - $this->user = ClubsUser::loadUser((int) $id); - $this->isNew = false; - } - else - throw new Exception('Need a user id.'); - - if($input->get->get('data', null, 'json') != null) - { - // Restore previous data - $dataurl = $input->get->get('data', null, 'json'); - $data = json_decode($dataurl, true); - - $this->user->setUser($data['user'], true); - $this->user->setName($data['name']); - $this->user->setAddress($data['address']); - $this->user->setCity($data['city']); - $this->user->setMail($data['mail']); - $this->user->setPhone($data['phone']); - $this->user->setMobile($data['mobile']); - - } - - ToolbarHelper::title('Club-Management - Person'); + ToolbarHelper::title('Club-Management - Person', 'user'); parent::display($tpl); } + protected function getControllerName() + { + return 'user'; + } + + protected function getFactory() + { + return new CommonClubsModelFactoryUser(); + } + + } From 319911d52fa13b468418b6786f40cffa816f6116 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Mon, 3 Jun 2019 14:51:37 +0200 Subject: [PATCH 26/41] Saving of test cases --- src/admin/views/test/tmpl/foo.php | 6 +++++ src/admin/views/test/view.html.php | 36 +++++++++++++++--------------- 2 files changed, 24 insertions(+), 18 deletions(-) create mode 100644 src/admin/views/test/tmpl/foo.php diff --git a/src/admin/views/test/tmpl/foo.php b/src/admin/views/test/tmpl/foo.php new file mode 100644 index 0000000..8b7e2d6 --- /dev/null +++ b/src/admin/views/test/tmpl/foo.php @@ -0,0 +1,6 @@ +club = $this->clubs[0]; -// $c = $factory->loadById(1); -// $p = $c->getPresident(); -// $id = $p->getId(); + $c = $factory->loadById(1); + $p = $c->getPresident(); + $id = $p->getId(); -// $places = $c->getPlaces(); -// // $places[0]->setName("abc"); -// // $places[0]->save(); + $places = $c->getPlaces(); +// $places[0]->setName("abc"); +// $places[0]->save(); -// $pfactory = new CommonClubsModelFactoryPlace(); -// $np = $pfactory->createNew(); -// $np->setName('MyName'); -// $np->setClub($c); -// // $np->save(); -// $np->getId(); + $pfactory = new CommonClubsModelFactoryPlace(); + $np = $pfactory->createNew(); + $np->setName('MyName'); + $np->setClub($c); +// $np->save(); + $np->getId(); -// $np = $c->getPlaces()[2]; -// $np->getName(); -// // $np->setName('foo2 with new Name'); -// // $np->save(); -// // $np->delete(); -// $this->log = $np; + $np = $c->getPlaces()[2]; + $np->getName(); +// $np->setName('foo2 with new Name'); +// $np->save(); +// $np->delete(); + $this->log = $np; parent::display($tpl); } From 1b43ab8356ea60112c2efed0e01cc7f641b84694 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Mon, 3 Jun 2019 15:29:08 +0200 Subject: [PATCH 27/41] Updated abstract controller to allow common CRUD operations, errors not yet testet --- src/admin/abstract/controller.php | 140 ++++++-------------- src/admin/abstract/view/single.php | 22 --- src/admin/common/abstract/model.php | 7 + src/admin/common/abstract/model/factory.php | 11 +- src/admin/controllers/position.php | 15 ++- 5 files changed, 70 insertions(+), 125 deletions(-) diff --git a/src/admin/abstract/controller.php b/src/admin/abstract/controller.php index 178f9ec..947f26f 100644 --- a/src/admin/abstract/controller.php +++ b/src/admin/abstract/controller.php @@ -9,79 +9,75 @@ defined('_JEXEC') or die; abstract class AbstractClubsController extends BaseController { + /** + * @return AbstractCommonClubsModelFactory + */ + protected abstract function getFactory(); - protected abstract function getNameOfElement(); + /** + * @return string The name of the underlying object in lower letters. + */ + protected abstract function getSingleBaseName(); - protected function getModelName() + /** + * @return string The name of the view to show a single object + */ + protected function getSingleViewName() { - return $this->getNameOfElement(); - } - - protected function getNameOfView() - { - return strtolower($this->getNameOfElement()); + return $this->getSingleBaseName(); } protected abstract function getDataMapping(); - function new() + public function new() { - $obj = call_user_func(array('Clubs' . $this->getNameOfElement(), 'create' . $this->getNameOfElement())); + $factory = $this->getFactory(); + $obj = $factory->createNew(); - // Fetch the posted data - $values = $this->loadData(); - - $this->filterPreCheck($values); - - // Check the input data - $error = ! $this->checkDataIsValid($values, true, Null); - - $view = $this->getNameOfView(); - - if($error) - { - $urldata = $this->packData($values); - $this->setRedirect(Route::_("index.php?option=com_clubs&view={$view}&id=new&data={$urldata}", false)); - return; - } - - $this->applyData($obj, $values); - - // Do the actual work - $obj->save(); - $this->setRedirect(Route::_("index.php?option=com_clubs&view={$view}s", false)); + $this->saveToDatabase($obj, 'new'); } - function change() + public function change() { $app = Factory::getApplication(); $input = $app->input; $id = (int) $input->post->getInt('id'); - $obj = call_user_func(array('Clubs' . $this->getNameOfElement(), 'load' . $this->getNameOfElement()), (int) $id); + $factory = $this->getFactory(); + $obj = $factory->loadById($id); + $this->saveToDatabase($obj, $id); + } + + /** + * @param AbstractCommonClubsModel $obj + * @param int $id + */ + protected function saveToDatabase($obj, $id) + { // Fetch the posted data $values = $this->loadData(); $this->filterPreCheck($values); // Check the input data - $error = ! $this->checkDataIsValid($values, false, $obj); + $error = ! $this->checkDataIsValid($values, $obj); - $view = $this->getNameOfView(); + $view = $this->getSingleViewName(); if($error) { - $urldata = $this->packData($values); + $urldata = $obj->pack(); $this->setRedirect(Route::_("index.php?option=com_clubs&view={$view}&id={$id}&data={$urldata}", false)); return; } - $this->applyData($obj, $values); + $obj->setValues($values); // Do the actual work $obj->save(); $this->setRedirect(Route::_("index.php?option=com_clubs&view={$view}s", false)); + } protected function loadData() @@ -101,7 +97,12 @@ abstract class AbstractClubsController extends BaseController protected function filterPreCheck(&$values){} - protected function checkDataIsValid($values, bool $isNew, $obj) + /** + * @param array $values + * @param AbstractCommonClubsModel $obj + * @return boolean + */ + protected function checkDataIsValid($values, $obj) { $error = false; // Check existence of the required fields @@ -140,72 +141,19 @@ abstract class AbstractClubsController extends BaseController return true; } - private function packData($values) - { - // FIXME Multiple bugs: filtering not working as expected and Mapping msut be checked - $this->filterPrePacking($values); - - $data = array(); - foreach($this->getDataMapping() as $m => $i) - { - if(isset($values[$m])) - $data[$m] = $values[$m]; - } - $json = json_encode($data); - return urlencode($json); - } - - protected function filterPrePacking(&$values){} - - public function applyData($obj, $values) - { - $this->applyDataToObject($obj, $values, $this->getDataMapping()); - } - - protected function applyDataToObject($obj, $values, $mapping) - { - foreach($mapping as $m => $v) - { - $functionName = $this->getSetterMethodName($m, $v); - - if($functionName === null) - { - continue; - } - - if(isset($v['skip_null_check'])) - $value = $values[$m]; - else - $value = (isset($values[$m]) && strlen($values[$m]) > 0) ? $values[$m] : null; - - $obj->$functionName($value); - } - } - - private function getSetterMethodName($m, $options) - { - if(array_key_exists('setter', $options)) - return $options['setter']; - - $firstChar = substr($m, 0, 1); - $restChars = substr($m, 1); - return 'set' . strtoupper($firstChar) . $restChars; - } - function delete() { $app = Factory::getApplication(); $id = $app->input->get->getInt('id'); - $name = $this->getNameOfElement(); + $name = $this->getSingleBaseName(); $app->enqueueMessage("Removal of $name with id $id."); - $className = 'Clubs' . $this->getModelName(); - $functionName = 'load' . $this->getModelName(); - $element = call_user_func(array($className, $functionName), $id); + $factory = $this->getFactory(); + $element = $factory->loadById($id); $element->delete(); - $view = $this->getNameOfView(); + $view = $this->getSingleViewName(); $this->setRedirect(Route::_("index.php?option=com_clubs&view={$view}s", false)); } diff --git a/src/admin/abstract/view/single.php b/src/admin/abstract/view/single.php index 8afa862..8df9641 100644 --- a/src/admin/abstract/view/single.php +++ b/src/admin/abstract/view/single.php @@ -60,30 +60,8 @@ abstract class AbstractClubsViewSingle extends HtmlView parent::display($tpl); } -// protected abstract function getViewName(); - protected abstract function getControllerName(); -// protected function getModelName() -// { -// $name = $this->getViewName(); -// return $this->capitalize($name); -// } - -// protected function getModelClass() -// { -// return 'Clubs' . $this->getModelName(); -// } - -// private function capitalize($s) -// { -// $first = substr($s, 0, 1); -// $rest = substr($s, 1); -// return strtoupper($first) . $rest; -// } - -// protected abstract function getElementController(); - /** * @return AbstractCommonClubsModelFactory */ diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index bdb0224..7e97b67 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -319,6 +319,9 @@ abstract class AbstractCommonClubsModel return $values; } + /** + * @return string + */ public function pack() { $vals = $this->getValues(); @@ -330,6 +333,10 @@ abstract class AbstractCommonClubsModel return urlencode($json); } + /** + * @param string $str + * @param boolean $decode + */ public function unpack($str, $decode = false) { if($decode) diff --git a/src/admin/common/abstract/model/factory.php b/src/admin/common/abstract/model/factory.php index 1513bcd..73a2514 100644 --- a/src/admin/common/abstract/model/factory.php +++ b/src/admin/common/abstract/model/factory.php @@ -96,7 +96,7 @@ abstract class AbstractCommonClubsModelFactory * * @param string $condition * @param string|array $sorting - * @return array + * @return AbstractCommonClubsModel[] */ public function loadElements($condition = null, $sorting = null, $callback = null) { @@ -138,8 +138,8 @@ abstract class AbstractCommonClubsModelFactory } /** - * * @param int $id + * @return AbstractCommonClubsModel */ public function loadById($id) { $arr = $this->loadElements("main.id = " . ((int)$id) ); @@ -159,6 +159,10 @@ abstract class AbstractCommonClubsModelFactory return $obj; } + /** + * @param array $row + * @return AbstractCommonClubsModel + */ protected function generateObject($row) { $obj = $this->generatePlainObject($row['id']); @@ -173,6 +177,9 @@ abstract class AbstractCommonClubsModelFactory return $obj; } + /** + * @return AbstractCommonClubsModel + */ public function createNew() { $obj = $this->generatePlainObject('new'); diff --git a/src/admin/controllers/position.php b/src/admin/controllers/position.php index f647c8d..2c0cf9a 100644 --- a/src/admin/controllers/position.php +++ b/src/admin/controllers/position.php @@ -6,16 +6,21 @@ defined('_JEXEC') or die; class ClubsControllerPosition extends AbstractClubsController { - protected function getNameOfElement() - { - return 'position'; - } - protected function getDataMapping() { return array( 'name'=>array('required'=>true, 'filter'=>'string', 'name'=>'Bezeichung') ); } + protected function getFactory() + { + return new CommonClubsModelFactoryPosition(); + } + + protected function getSingleBaseName() + { + return 'position'; + } + } From 616a0b7dd999ba6153dde5de6453797f2d41e491 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Mon, 3 Jun 2019 15:42:25 +0200 Subject: [PATCH 28/41] Added code after day of work --- src/admin/abstract/controller.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/admin/abstract/controller.php b/src/admin/abstract/controller.php index 947f26f..6c6efd8 100644 --- a/src/admin/abstract/controller.php +++ b/src/admin/abstract/controller.php @@ -27,6 +27,9 @@ abstract class AbstractClubsController extends BaseController return $this->getSingleBaseName(); } + /** + * @todo Make this OO conforme + */ protected abstract function getDataMapping(); public function new() From 16e7ed0bc0f222fa6a7bdf9b71343aeee2e4392d Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Tue, 4 Jun 2019 16:51:50 +0200 Subject: [PATCH 29/41] Simple controllers (offer and position) are workign in the new oo format --- src/admin/abstract/controller.php | 118 ++++++++++++------ src/admin/clubs.php | 1 + .../common/abstract/controller/mapping.php | 55 ++++++++ src/admin/common/abstract/model.php | 12 +- src/admin/common/abstract/model/column.php | 19 +-- src/admin/common/controllermappings/cmp.php | 14 +++ src/admin/common/controllermappings/float.php | 14 +++ src/admin/common/controllermappings/int.php | 14 +++ src/admin/common/controllermappings/ref.php | 44 +++++++ .../common/controllermappings/string.php | 14 +++ src/admin/common/models/column/ref.php | 4 +- src/admin/common/models/factory/offer.php | 2 +- src/admin/common/models/factory/position.php | 2 +- src/admin/controllers/club.php | 13 +- src/admin/controllers/offer.php | 9 +- src/admin/controllers/position.php | 6 - src/admin/controllers/user.php | 4 +- 17 files changed, 275 insertions(+), 70 deletions(-) create mode 100644 src/admin/common/abstract/controller/mapping.php create mode 100644 src/admin/common/controllermappings/cmp.php create mode 100644 src/admin/common/controllermappings/float.php create mode 100644 src/admin/common/controllermappings/int.php create mode 100644 src/admin/common/controllermappings/ref.php create mode 100644 src/admin/common/controllermappings/string.php diff --git a/src/admin/abstract/controller.php b/src/admin/abstract/controller.php index 6c6efd8..e80f41c 100644 --- a/src/admin/abstract/controller.php +++ b/src/admin/abstract/controller.php @@ -7,6 +7,9 @@ use Joomla\CMS\Router\Route; // No direct access. defined('_JEXEC') or die; +class DataParsingException extends Exception {} +class DataInvalidException extends Exception {} + abstract class AbstractClubsController extends BaseController { /** @@ -27,11 +30,6 @@ abstract class AbstractClubsController extends BaseController return $this->getSingleBaseName(); } - /** - * @todo Make this OO conforme - */ - protected abstract function getDataMapping(); - public function new() { $factory = $this->getFactory(); @@ -58,67 +56,109 @@ abstract class AbstractClubsController extends BaseController */ protected function saveToDatabase($obj, $id) { - // Fetch the posted data - $values = $this->loadData(); - - $this->filterPreCheck($values); - - // Check the input data - $error = ! $this->checkDataIsValid($values, $obj); - - $view = $this->getSingleViewName(); - - if($error) + try { + // Fetch the posted data + $values = $this->loadData(); + + $this->filterRawCheck($values); + + // Check the input data + if( ! $this->requiredDataIsAvailable($values) ) + throw new DataParsingException(); + + if( ! $this->rawDataIsValid($values) ) + throw new DataParsingException(); + + $obj->setValues($values, true); + + // Do some additional tests by the controller + if( ! $this->objectValid($obj) ) + throw new DataInvalidException(); + + // Check if the object complains about valitity + if( ! $obj->dataIsValid() ) + throw new DataInvalidException(); + + // Do the actual work + $obj->save(); + + // Redirect to the list of objects + $view = $this->getSingleViewName(); + $this->setRedirect(Route::_("index.php?option=com_clubs&view={$view}s", false)); + } + catch(DataParsingException $e) + { + // FIXME Make this robust (are external refs already dereferenced?) + $view = $this->getSingleViewName(); + $obj->setValues($values, true); + $urldata = $obj->pack(); + $this->setRedirect(Route::_("index.php?option=com_clubs&view={$view}&id={$id}&data={$urldata}", false)); + } + catch(DataInvalidException $e) + { + $view = $this->getSingleViewName(); $urldata = $obj->pack(); $this->setRedirect(Route::_("index.php?option=com_clubs&view={$view}&id={$id}&data={$urldata}", false)); - return; } - - $obj->setValues($values); - - // Do the actual work - $obj->save(); - $this->setRedirect(Route::_("index.php?option=com_clubs&view={$view}s", false)); - } protected function loadData() { $values = array(); + $factory = $this->getFactory(); $input = Factory::getApplication()->input->post; - foreach($this->getDataMapping() as $m => $f) + foreach($factory->getAttributes() as $column) { - $filter = (isset($f['filter'])) ? $f['filter'] : 'string'; - - $values[$m] = $input->get($m, null, $filter); + $values[$column->getAlias()] = $column->getFilter()->getFilteredValue($input, $column->getAlias()); } return $values; } - protected function filterPreCheck(&$values){} + protected function filterRawCheck(&$values){} + + protected function objectValid($obj) + { + return true; + } + + private function requiredDataIsAvailable($values) + { + $ok = true; + + foreach($this->getFactory()->getAttributes() as $column) + { + $filter = $column->getFilter(); + if(! $filter->requiredDataAvailable($values[$column->getAlias()])) + { + $fname = $filter->getName(); + Factory::getApplication()->enqueueMessage("Das Feld $fname ist obligatorisch.", 'error'); + $ok = false; + } + } + + return $ok; + } /** * @param array $values * @param AbstractCommonClubsModel $obj * @return boolean */ - protected function checkDataIsValid($values, $obj) + protected function rawDataIsValid($values) { $error = false; - // Check existence of the required fields - foreach ($this->getDataMapping() as $m => $v) + + $factory = $this->getFactory(); + + foreach($factory->getAttributes() as $column) { - if(! isset($v['required']) || ! $v['required']) - continue; - - // Field is required - if(! $this->fieldValid($m, $values[$m], $v)) + if(! $column->getFilter()->rawValueValid($values[$column->getAlias()])) { - $fname = (isset($v['name'])) ? $v['name'] : $m; - Factory::getApplication()->enqueueMessage("Das Feld $fname ist obligatorisch.", 'error'); + $fname = $column->getFilter()->getName(); + Factory::getApplication()->enqueueMessage("Das Feld $fname ist fehlerhaft.", 'error'); $error = true; } } diff --git a/src/admin/clubs.php b/src/admin/clubs.php index debe58e..726f58f 100644 --- a/src/admin/clubs.php +++ b/src/admin/clubs.php @@ -10,6 +10,7 @@ JLoader::discover('Clubs', JPATH_ROOT . '/administrator/components/com_clubs/mym JLoader::registerPrefix('AbstractClubs', JPATH_ROOT . '/administrator/components/com_clubs/abstract'); JLoader::registerPrefix('AbstractCommonClubs', JPATH_ROOT . '/administrator/components/com_clubs/common/abstract'); JLoader::registerPrefix('CommonClubsModel', JPATH_ROOT . '/administrator/components/com_clubs/common/models'); +JLoader::registerPrefix('CommonClubsControllerMapping', JPATH_ROOT . '/administrator/components/com_clubs/common/controllermappings'); $controller = BaseController::getInstance("Clubs"); $input = Factory::getApplication()->input; diff --git a/src/admin/common/abstract/controller/mapping.php b/src/admin/common/abstract/controller/mapping.php new file mode 100644 index 0000000..b445609 --- /dev/null +++ b/src/admin/common/abstract/controller/mapping.php @@ -0,0 +1,55 @@ +name = $name; + $this->required = $required; + } + + + /** + * @param mixed $value + * @return bool + */ + public function requiredDataAvailable($value) + { + if($this->required && ($value === null || $value === '')) + return false; + + return true; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param JInput $input + * @param string $name + * @return string + */ + public abstract function getFilteredValue($input, $name); + + /** + * @param mixed $value + * @return boolean + */ + public function rawValueValid($value) + { + return true; + } + +} diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 7e97b67..806263c 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -40,9 +40,12 @@ abstract class AbstractCommonClubsModel return $this->new; } - public function setValues($values) + public function setValues($values, $unpack = false) { - $this->values = $values; + if($unpack) + $this->values = $this->unpackExternalReferencesFromKeys($values); + else + $this->values = $values; } protected function setValue($key, $value) @@ -351,4 +354,9 @@ abstract class AbstractCommonClubsModel $this->setValues($vals); } + public function dataIsValid() + { + return true; + } + } diff --git a/src/admin/common/abstract/model/column.php b/src/admin/common/abstract/model/column.php index a21ceee..cfacc87 100644 --- a/src/admin/common/abstract/model/column.php +++ b/src/admin/common/abstract/model/column.php @@ -8,7 +8,10 @@ abstract class AbstractCommonClubsModelColumn protected $alias; protected $column; - protected $required; + /** + * @var AbstractCommonClubsControllerMapping + */ + protected $filter; public function getAlias() { @@ -20,17 +23,12 @@ abstract class AbstractCommonClubsModelColumn return $this->column; } - public function isRequired() - { - return $this->required; - } - public abstract function isSimpleType(); - public function __construct($alias, $required = true, $column = null) + public function __construct($alias, $filter, $column = null) { $this->alias = $alias; - $this->required = $required; + $this->filter = $filter; if(isset($column)) $this->column = $column; else @@ -71,4 +69,9 @@ abstract class AbstractCommonClubsModelColumn return $value; } + public function getFilter() + { + return $this->filter; + } + } diff --git a/src/admin/common/controllermappings/cmp.php b/src/admin/common/controllermappings/cmp.php new file mode 100644 index 0000000..04a48c4 --- /dev/null +++ b/src/admin/common/controllermappings/cmp.php @@ -0,0 +1,14 @@ +getCmd($name); + } + +} diff --git a/src/admin/common/controllermappings/float.php b/src/admin/common/controllermappings/float.php new file mode 100644 index 0000000..a3167ae --- /dev/null +++ b/src/admin/common/controllermappings/float.php @@ -0,0 +1,14 @@ +getFloat($name); + } + +} diff --git a/src/admin/common/controllermappings/int.php b/src/admin/common/controllermappings/int.php new file mode 100644 index 0000000..bac15d7 --- /dev/null +++ b/src/admin/common/controllermappings/int.php @@ -0,0 +1,14 @@ +getInt($name); + } + +} diff --git a/src/admin/common/controllermappings/ref.php b/src/admin/common/controllermappings/ref.php new file mode 100644 index 0000000..24d9d3f --- /dev/null +++ b/src/admin/common/controllermappings/ref.php @@ -0,0 +1,44 @@ +factory = $factory; + } + + public function getFilteredValue($input, $name) + { + return $input->getInt($name); + } + + public function rawValueValid($value) + { + try + { + $this->factory->loadById((int) $value); + } + catch(ElementNotFoundException $e) + { + return false; + } + + return true; + } + +} diff --git a/src/admin/common/controllermappings/string.php b/src/admin/common/controllermappings/string.php new file mode 100644 index 0000000..5f14cfa --- /dev/null +++ b/src/admin/common/controllermappings/string.php @@ -0,0 +1,14 @@ +getString($name); + } + +} diff --git a/src/admin/common/models/column/ref.php b/src/admin/common/models/column/ref.php index 18b79e3..fe30bff 100644 --- a/src/admin/common/models/column/ref.php +++ b/src/admin/common/models/column/ref.php @@ -14,9 +14,9 @@ class CommonClubsModelColumnRef extends AbstractCommonClubsModelColumn protected $className; - public function __construct($alias, $className, $required=true, $column=null) + public function __construct($alias, $className, $column=null) { - parent::__construct($alias, $required, $column); + parent::__construct($alias, $column); if(empty($className)) throw new Exception('Classname must be non-empty.'); diff --git a/src/admin/common/models/factory/offer.php b/src/admin/common/models/factory/offer.php index 4f3a875..e75bdc9 100644 --- a/src/admin/common/models/factory/offer.php +++ b/src/admin/common/models/factory/offer.php @@ -8,7 +8,7 @@ class CommonClubsModelFactoryOffer extends AbstractCommonClubsModelFactory protected function fetchAttributes() { return array( - new CommonClubsModelColumnString('name') + new CommonClubsModelColumnString('name', new CommonClubsControllerMappingString('Bezeichnung')) ); } diff --git a/src/admin/common/models/factory/position.php b/src/admin/common/models/factory/position.php index 651a856..13395af 100644 --- a/src/admin/common/models/factory/position.php +++ b/src/admin/common/models/factory/position.php @@ -8,7 +8,7 @@ class CommonClubsModelFactoryPosition extends AbstractCommonClubsModelFactory protected function fetchAttributes() { return array( - new CommonClubsModelColumnString('name') + new CommonClubsModelColumnString('name', new CommonClubsControllerMappingString('Bezeichnung')) ); } diff --git a/src/admin/controllers/club.php b/src/admin/controllers/club.php index 8083f6a..2983c69 100644 --- a/src/admin/controllers/club.php +++ b/src/admin/controllers/club.php @@ -6,11 +6,12 @@ defined('_JEXEC') or die; class ClubsControllerClub extends AbstractClubsController { - protected function getNameOfElement() + + protected function getSingleBaseName() { return 'club'; } - + protected function getDataMapping() { return array( @@ -27,7 +28,7 @@ class ClubsControllerClub extends AbstractClubsController ); } - protected function filterPreCheck(&$values) + protected function filterRawCheck(&$values) { if(is_null($values['charitable'])) $values['charitable'] = false; @@ -41,4 +42,10 @@ class ClubsControllerClub extends AbstractClubsController { $values['president'] = $values['president']->getId(); } + protected function getFactory() + { + return new CommonClubsModelFactoryClub(); + } + + } diff --git a/src/admin/controllers/offer.php b/src/admin/controllers/offer.php index 384ba1e..323c928 100644 --- a/src/admin/controllers/offer.php +++ b/src/admin/controllers/offer.php @@ -6,18 +6,15 @@ defined('_JEXEC') or die; class ClubsControllerOffer extends AbstractClubsController { - protected function getNameOfElement() + protected function getSingleBaseName() { return 'offer'; } - protected function getDataMapping() + protected function getFactory() { - return array( - 'name' => array('required'=>true, 'name'=>'Bezeichnung', 'filter'=>'string') - ); + return new CommonClubsModelFactoryOffer(); } - } diff --git a/src/admin/controllers/position.php b/src/admin/controllers/position.php index 2c0cf9a..3810df6 100644 --- a/src/admin/controllers/position.php +++ b/src/admin/controllers/position.php @@ -6,12 +6,6 @@ defined('_JEXEC') or die; class ClubsControllerPosition extends AbstractClubsController { - protected function getDataMapping() - { - return array( - 'name'=>array('required'=>true, 'filter'=>'string', 'name'=>'Bezeichung') - ); - } protected function getFactory() { return new CommonClubsModelFactoryPosition(); diff --git a/src/admin/controllers/user.php b/src/admin/controllers/user.php index 4cd1f0d..b2a2a67 100644 --- a/src/admin/controllers/user.php +++ b/src/admin/controllers/user.php @@ -42,9 +42,9 @@ class ClubsControllerUser extends AbstractClubsController * {@inheritDoc} * @see AbstractClubsController::checkData() */ - protected function checkDataIsValid($values, $isNew, $obj) + protected function rawDataIsValid($values, $isNew, $obj) { - if(! parent::checkDataIsValid($values, $isNew, $obj)) + if(! parent::rawDataIsValid($values, $isNew, $obj)) return false; // TODO Auto-generated method stub From fc85e6b3229a699cac5b34f31efdb0a8ee7d66d2 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 5 Jun 2019 16:02:36 +0200 Subject: [PATCH 30/41] Made user controller working mostly. Not everything is tested but seems good --- src/admin/abstract/controller.php | 24 +++- src/admin/common/abstract/model.php | 25 +++- src/admin/common/abstract/model/factory.php | 10 +- .../controllermappings/{cmp.php => cmd.php} | 0 src/admin/common/models/factory/user.php | 16 +-- src/admin/common/models/user.php | 36 +++++- src/admin/controllers/club.php | 2 +- src/admin/controllers/user.php | 108 ++++++++++++------ 8 files changed, 170 insertions(+), 51 deletions(-) rename src/admin/common/controllermappings/{cmp.php => cmd.php} (100%) diff --git a/src/admin/abstract/controller.php b/src/admin/abstract/controller.php index e80f41c..0856cbe 100644 --- a/src/admin/abstract/controller.php +++ b/src/admin/abstract/controller.php @@ -59,9 +59,9 @@ abstract class AbstractClubsController extends BaseController try { // Fetch the posted data - $values = $this->loadData(); + $values = $this->loadData($this->additionalData()); - $this->filterRawCheck($values); + $this->filterRaw($values); // Check the input data if( ! $this->requiredDataIsAvailable($values) ) @@ -70,7 +70,9 @@ abstract class AbstractClubsController extends BaseController if( ! $this->rawDataIsValid($values) ) throw new DataParsingException(); - $obj->setValues($values, true); + $obj->applyAndMergeValues($values, true); + + $this->filterObject($obj, $values); // Do some additional tests by the controller if( ! $this->objectValid($obj) ) @@ -103,7 +105,12 @@ abstract class AbstractClubsController extends BaseController } } - protected function loadData() + protected function additionalData() + { + return array(); + } + + protected function loadData($additionalData) { $values = array(); $factory = $this->getFactory(); @@ -114,10 +121,17 @@ abstract class AbstractClubsController extends BaseController $values[$column->getAlias()] = $column->getFilter()->getFilteredValue($input, $column->getAlias()); } + foreach($additionalData as $k => $v) + { + $values[$k] = $v->getFilteredValue($input, $k); + } + return $values; } - protected function filterRawCheck(&$values){} + protected function filterRaw(&$values){} + + protected function filterObject($obj){} protected function objectValid($obj) { diff --git a/src/admin/common/abstract/model.php b/src/admin/common/abstract/model.php index 806263c..e0859c5 100644 --- a/src/admin/common/abstract/model.php +++ b/src/admin/common/abstract/model.php @@ -48,6 +48,27 @@ abstract class AbstractCommonClubsModel $this->values = $values; } + public function applyAndMergeValues($values, $unpack = true) + { + $vals = $this->getValues(); + + if($unpack) + $vals = $this->packExternalReferencesAsKeys($vals); + + foreach($this->getFactory()->getAttributes() as $column) + { + if(array_key_exists($column->getAlias(), $values)) + { + $vals[$column->getAlias()] = $values[$column->getAlias()]; + } + } + + if($unpack) + $vals = $this->unpackExternalReferencesFromKeys($vals); + + $this->setValues($vals, false); + } + protected function setValue($key, $value) { if(is_null($this->values)) @@ -134,6 +155,7 @@ abstract class AbstractCommonClubsModel $vals[$alias] = $a->packValue($vals[$alias]); } + // XXX Joins return $vals; } @@ -144,7 +166,8 @@ abstract class AbstractCommonClubsModel foreach($factory->getAttributes() as $a) { $alias = $a->getAlias(); - $vals[$alias] = $a->unpackValue($vals[$alias]); + if(isset($vals[$alias])) + $vals[$alias] = $a->unpackValue($vals[$alias]); } $joins = $factory->getJoins(); diff --git a/src/admin/common/abstract/model/factory.php b/src/admin/common/abstract/model/factory.php index 73a2514..434b72a 100644 --- a/src/admin/common/abstract/model/factory.php +++ b/src/admin/common/abstract/model/factory.php @@ -141,10 +141,16 @@ abstract class AbstractCommonClubsModelFactory * @param int $id * @return AbstractCommonClubsModel */ - public function loadById($id) { + public function loadById($id, $throwErr = true) + { $arr = $this->loadElements("main.id = " . ((int)$id) ); if(sizeof($arr) == 0) - throw new ElementNotFoundException(); + { + if($throwErr) + throw new ElementNotFoundException(); + else + return null; + } return $arr[0]; } diff --git a/src/admin/common/controllermappings/cmp.php b/src/admin/common/controllermappings/cmd.php similarity index 100% rename from src/admin/common/controllermappings/cmp.php rename to src/admin/common/controllermappings/cmd.php diff --git a/src/admin/common/models/factory/user.php b/src/admin/common/models/factory/user.php index 0341c30..3694cc0 100644 --- a/src/admin/common/models/factory/user.php +++ b/src/admin/common/models/factory/user.php @@ -8,14 +8,14 @@ class CommonClubsModelFactoryUser extends AbstractCommonClubsModelFactory public function fetchAttributes() { return array( - new CommonClubsModelColumnString('user'), - new CommonClubsModelColumnString('name'), - new CommonClubsModelColumnString('password'), - new CommonClubsModelColumnString('address'), - new CommonClubsModelColumnString('city'), - new CommonClubsModelColumnString('mail'), - new CommonClubsModelColumnString('phone'), - new CommonClubsModelColumnString('mobile') + new CommonClubsModelColumnString('user', new CommonClubsControllerMappingCmd('Benutzername')), + new CommonClubsModelColumnString('name', new CommonClubsControllerMappingString('Bürgerlicher Name')), + new CommonClubsModelColumnString('password', new CommonClubsControllerMappingString('Passwort', false)), + new CommonClubsModelColumnString('address', new CommonClubsControllerMappingString('Adresse')), + new CommonClubsModelColumnString('city', new CommonClubsControllerMappingString('Stadt')), + new CommonClubsModelColumnString('mail', new CommonClubsControllerMappingString('E-Mail')), + new CommonClubsModelColumnString('phone', new CommonClubsControllerMappingString('Telefonnummer', false)), + new CommonClubsModelColumnString('mobile', new CommonClubsControllerMappingString('Handynummer', false)) ); } diff --git a/src/admin/common/models/user.php b/src/admin/common/models/user.php index 62f880e..0ad1b5e 100644 --- a/src/admin/common/models/user.php +++ b/src/admin/common/models/user.php @@ -114,6 +114,12 @@ class CommonClubsModelUser extends AbstractCommonClubsModel $this->setValue('password', $hash); } + public function isPasswordSet() + { + $password = $this->getValues()['password']; + return isset($password) && strlen($password) > 0; + } + public function getPositions() { return $this->fetchAssociatedElements(new CommonClubsModelFactoryUserassoc(), 'userid'); @@ -139,7 +145,7 @@ class CommonClubsModelUser extends AbstractCommonClubsModel return true; } - public function isUsernameSuitable($user) + public function isUsernameFree($user) { $factory = new CommonClubsModelFactoryUser(); $users = $factory->loadElements(null, null, function($q) use ($user){ @@ -167,5 +173,33 @@ class CommonClubsModelUser extends AbstractCommonClubsModel $db->execute(); } + public function dataIsValid() + { + if(! parent::dataIsValid()) + return false; + + if(! $this->usernameIsValid()) + { + return false; + } + + return true; + } + + private function usernameIsValid() + { + $factory = $this->getFactory(); + $medb = $factory->loadById($this->getId(), false); + + if($medb !== null && $medb->getUserName() === $this->getUsername()) + // No change was made + return true; + + if(! $this->isUsernameFree($this->getUsername()) ) + return false; + + return true; + } + } \ No newline at end of file diff --git a/src/admin/controllers/club.php b/src/admin/controllers/club.php index 2983c69..b6114c3 100644 --- a/src/admin/controllers/club.php +++ b/src/admin/controllers/club.php @@ -28,7 +28,7 @@ class ClubsControllerClub extends AbstractClubsController ); } - protected function filterRawCheck(&$values) + protected function filterRaw(&$values) { if(is_null($values['charitable'])) $values['charitable'] = false; diff --git a/src/admin/controllers/user.php b/src/admin/controllers/user.php index b2a2a67..4fd6a49 100644 --- a/src/admin/controllers/user.php +++ b/src/admin/controllers/user.php @@ -18,7 +18,7 @@ class ClubsControllerUser extends AbstractClubsController return ClubsUser::isUserNameFree($username, $id); } - protected function getNameOfElement() + protected function getSingleBaseName() { return 'user'; } @@ -38,49 +38,44 @@ class ClubsControllerUser extends AbstractClubsController ); } - /** - * {@inheritDoc} - * @see AbstractClubsController::checkData() - */ - protected function rawDataIsValid($values, $isNew, $obj) + + protected function rawDataIsValid($values) { - if(! parent::rawDataIsValid($values, $isNew, $obj)) + if(! parent::rawDataIsValid($values)) return false; - // TODO Auto-generated method stub - if(isset($values['pwd']) && strlen($values['pwd']) > 0) + if($this->passwordIsSet($values)) { - $pwd = $values['pwd']; - $pwdConfirm = $values['pwdConfirm']; - - if(trim($pwd) != trim($pwdConfirm)) - { - Factory::getApplication()->enqueueMessage('Die Passwörter stimmen nicht überein.', 'error'); + if( ! $this->passwordIsValid($values)) return false; - } - - if(! ClubsUser::checkPasswordStrength(trim($pwd))) - { - Factory::getApplication()->enqueueMessage('Das Passwort ist zu schwach.', 'error'); - return false; - } - - } - else - { - if($isNew) - { - Factory::getApplication()->enqueueMessage('Für einen neuen Benutzer muss ein Passwort vergeben werden.', 'error'); - return false; - } } - if(! $this->checkUserName(trim($values['user']), $obj)) + return true; + } + + private function passwordIsSet($values) + { + return isset($values['pwd']) && strlen($values['pwd']) > 0; + } + + private function passwordIsValid($values) + { + $pwd = $values['pwd']; + $pwdConfirm = $values['pwdConfirm']; + + if(trim($pwd) != trim($pwdConfirm)) { - Factory::getApplication()->enqueueMessage('Username ' . $$values['user'] . ' ist nicht gültig.', 'error'); + Factory::getApplication()->enqueueMessage('Die Passwörter stimmen nicht überein.', 'error'); return false; } + // FIXME Check password strength +// if(! ClubsUser::checkPasswordStrength(trim($pwd))) +// { +// Factory::getApplication()->enqueueMessage('Das Passwort ist zu schwach.', 'error'); +// return false; +// } + return true; } @@ -112,8 +107,55 @@ class ClubsControllerUser extends AbstractClubsController $this->applyDataToObject($obj, $values, $mapping); } + + protected function getFactory() + { + return new CommonClubsModelFactoryUser(); + } + /** + * + * {@inheritDoc} + * @see AbstractClubsController::filterObject() + * @param CommonClubsModelUser $obj + */ + protected function filterObject($obj, $values) + { +// if($obj->isNew() && (empty($values['pwd']) || strlen($values['pwd']) == 0) ) + + if(isset($values['pwd']) && strlen($values['pwd']) > 0) + { + $obj->setPassword($values['pwd']); + } + } + + /** + * {@inheritDoc} + * @see AbstractClubsController::objectValid() + * @param CommonClubsModelUser $obj + */ + protected function objectValid($obj) + { + if(! $obj->isPasswordSet()) + { + Factory::getApplication()->enqueueMessage('Kein Passwort wurde vergeben.', 'error'); + return false; + } + + return true; + } + protected function filterRaw(&$values) + { + unset($values['password']); + } + protected function additionalData() + { + return array( + 'pwd' => new CommonClubsControllerMappingString('Passwort'), + 'pwdConfirm' => new CommonClubsControllerMappingString('Passwortwiederholung') + ); + } } From 60abc189ecdcb1e6a2f440e24845d7d781062d9d Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 5 Jun 2019 16:04:30 +0200 Subject: [PATCH 31/41] Tidied up controller class --- src/admin/controllers/user.php | 55 ---------------------------------- 1 file changed, 55 deletions(-) diff --git a/src/admin/controllers/user.php b/src/admin/controllers/user.php index 4fd6a49..a6af1e8 100644 --- a/src/admin/controllers/user.php +++ b/src/admin/controllers/user.php @@ -8,36 +8,10 @@ defined('_JEXEC') or die; class ClubsControllerUser extends AbstractClubsController { - private function checkUserName($username, $obj = null) - { - $id = -1; - - if(!is_null($obj)) - $id = $obj->getId(); - - return ClubsUser::isUserNameFree($username, $id); - } - protected function getSingleBaseName() { return 'user'; } - - protected function getDataMapping() - { - return array( - 'user'=>array('required'=>true, 'name'=>'Benutzername', 'filter'=>'cmd'), - 'pwd'=>array('required'=>false, 'name'=>'Passwort', 'filter'=>'string', 'setter'=>'setPassword'), - 'pwdConfirm'=>array('required'=>false, 'name'=>'Passwortwiederholung', 'filter'=>'string', 'setter'=>null), - 'name'=>array('required'=>true, 'name'=>'Bürgerlicher Name', 'filter'=>'string'), - 'address'=>array('required'=>true, 'name'=>'Adresse', 'filter'=>'string'), - 'city'=>array('required'=>true, 'name'=>'Stadt', 'filter'=>'string'), - 'mail'=>array('required'=>true, 'name'=>'E-Mail', 'filter'=>'string'), - 'phone'=>array('required'=>false, 'name'=>'Telefonnummer', 'filter'=>'string'), - 'mobile'=>array('required'=>false, 'name'=>'Handynummer', 'filter'=>'string') - - ); - } protected function rawDataIsValid($values) { @@ -79,35 +53,6 @@ class ClubsControllerUser extends AbstractClubsController return true; } - /** - * {@inheritDoc} - * @see AbstractClubsController::filterPrePacking() - */ - protected function filterPrePacking(&$values) - { - parent::filterPrePacking($values); - unset($values['pwd']); - unset($values['pwdConfirm']); - } - - /** - * {@inheritDoc} - * @see AbstractClubsController::applyData() - */ - public function applyData($obj, $values) - { - // TODO Auto-generated method stub - $mapping = $this->getDataMapping(); - - if(strlen($values['pwd']) == 0) - { - unset($values['pwd']); - unset($mapping['pwd']); - } - - $this->applyDataToObject($obj, $values, $mapping); - } - protected function getFactory() { return new CommonClubsModelFactoryUser(); From 40b88859cd0c3358ea5639e661781ac1c7b80dac Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 5 Jun 2019 16:58:23 +0200 Subject: [PATCH 32/41] Updated user view in order to show referenced clubs --- src/admin/abstract/view/single.php | 3 ++ src/admin/common/models/column/ref.php | 4 +- src/admin/common/models/factory/club.php | 18 ++++---- .../common/models/factory/offerassoc.php | 4 +- src/admin/common/models/factory/place.php | 6 +-- src/admin/common/models/factory/userassoc.php | 16 +++---- src/admin/views/user/tmpl/default.php | 22 ++++++++++ src/admin/views/user/view.html.php | 43 +++++++++++++++++++ src/admin/views/users/tmpl/default.php | 1 - 9 files changed, 92 insertions(+), 25 deletions(-) diff --git a/src/admin/abstract/view/single.php b/src/admin/abstract/view/single.php index 8df9641..92846f3 100644 --- a/src/admin/abstract/view/single.php +++ b/src/admin/abstract/view/single.php @@ -16,6 +16,7 @@ abstract class AbstractClubsViewSingle extends HtmlView */ protected $object; protected $isNew; + protected $id; private $prepared = FALSE; @@ -50,6 +51,8 @@ abstract class AbstractClubsViewSingle extends HtmlView $this->object->unpack($jsonData); } + + $this->id = $id; } public function display($tpl = null) diff --git a/src/admin/common/models/column/ref.php b/src/admin/common/models/column/ref.php index fe30bff..fcd91e9 100644 --- a/src/admin/common/models/column/ref.php +++ b/src/admin/common/models/column/ref.php @@ -14,9 +14,9 @@ class CommonClubsModelColumnRef extends AbstractCommonClubsModelColumn protected $className; - public function __construct($alias, $className, $column=null) + public function __construct($alias, $className, $column, $filter) { - parent::__construct($alias, $column); + parent::__construct($alias, $filter, $column); if(empty($className)) throw new Exception('Classname must be non-empty.'); diff --git a/src/admin/common/models/factory/club.php b/src/admin/common/models/factory/club.php index 6331ad6..684db95 100644 --- a/src/admin/common/models/factory/club.php +++ b/src/admin/common/models/factory/club.php @@ -8,15 +8,15 @@ class CommonClubsModelFactoryClub extends AbstractCommonClubsModelFactory public function fetchAttributes() { return array( - new CommonClubsModelColumnString('name'), - new CommonClubsModelColumnString('address'), - new CommonClubsModelColumnString('city'), - new CommonClubsModelColumnString('homepage'), - new CommonClubsModelColumnString('mail'), - new CommonClubsModelColumnString('iban'), - new CommonClubsModelColumnString('bic'), - new CommonClubsModelColumnInt('charitable'), - new CommonClubsModelColumnRef('president', 'CommonClubsModelUser') + new CommonClubsModelColumnString('name', new CommonClubsControllerMappingString('Clubname')), + new CommonClubsModelColumnString('address', new CommonClubsControllerMappingString('Adresse')), + new CommonClubsModelColumnString('city', new CommonClubsControllerMappingString('Stadt')), + new CommonClubsModelColumnString('homepage', new CommonClubsControllerMappingString('Homepaage', false)), + new CommonClubsModelColumnString('mail', new CommonClubsControllerMappingString('E-Mail')), + new CommonClubsModelColumnString('iban', new CommonClubsControllerMappingCmd('IBAN')), + new CommonClubsModelColumnString('bic', new CommonClubsControllerMappingCmd('BIC')), + new CommonClubsModelColumnInt('charitable', new CommonClubsControllerMappingInt('Gemeinnützigkeit')), + new CommonClubsModelColumnRef('president', 'CommonClubsModelUser', 'president', new CommonClubsControllerMappingRef('Vorsitzender', new CommonClubsModelFactoryUser())) ); } diff --git a/src/admin/common/models/factory/offerassoc.php b/src/admin/common/models/factory/offerassoc.php index 14688b0..e552fdb 100644 --- a/src/admin/common/models/factory/offerassoc.php +++ b/src/admin/common/models/factory/offerassoc.php @@ -8,8 +8,8 @@ class CommonClubsModelFactoryOfferassoc extends AbstractCommonClubsModelFactory protected function fetchAttributes() { return array( - new CommonClubsModelColumnRef('club', 'CommonClubsModelClub', true, 'clubid'), - new CommonClubsModelColumnRef('offer', 'CommonClubsModelOffer', true, 'offerid') + new CommonClubsModelColumnRef('club', 'CommonClubsModelClub', 'clubid', new CommonClubsControllerMappingRef('Club', new CommonClubsModelFactoryClub())), + new CommonClubsModelColumnRef('offer', 'CommonClubsModelOffer', 'offerid', new CommonClubsControllerMappingRef('Angebot', new CommonClubsModelFactoryOffer())) ); } diff --git a/src/admin/common/models/factory/place.php b/src/admin/common/models/factory/place.php index 01b40c6..a24565d 100644 --- a/src/admin/common/models/factory/place.php +++ b/src/admin/common/models/factory/place.php @@ -8,9 +8,9 @@ class CommonClubsModelFactoryPlace extends AbstractCommonClubsModelFactory public function fetchAttributes() { return array( - new CommonClubsModelColumnString('name'), - new CommonClubsModelColumnRef('club', 'CommonClubsModelClub', true, 'clubid'), - new CommonClubsModelColumnInt('area', false) + new CommonClubsModelColumnString('name', new CommonClubsControllerMappingString('Bezeichnung')), + new CommonClubsModelColumnRef('club', 'CommonClubsModelClub', 'clubid', new CommonClubsControllerMappingRef('Club', new CommonClubsModelFactoryClub())), + new CommonClubsModelColumnInt('area', new CommonClubsControllerMappingInt('Fläche', false)) ); } diff --git a/src/admin/common/models/factory/userassoc.php b/src/admin/common/models/factory/userassoc.php index 49e9c1b..a046e10 100644 --- a/src/admin/common/models/factory/userassoc.php +++ b/src/admin/common/models/factory/userassoc.php @@ -8,14 +8,14 @@ class CommonClubsModelFactoryUserassoc extends AbstractCommonClubsModelFactory protected function fetchAttributes() { return array( - new CommonClubsModelColumnRef('user', 'CommonClubsModelUser', true, 'userid'), - new CommonClubsModelColumnRef('club', 'CommonClubsModelClub', true, 'clubid'), - new CommonClubsModelColumnRef('position', 'CommonClubsModelPosition', true, 'positionid'), - new CommonClubsModelColumnInt('admin'), - new CommonClubsModelColumnString('address', false), - new CommonClubsModelColumnString('mail', false), - new CommonClubsModelColumnString('phone', false), - new CommonClubsModelColumnString('state') + new CommonClubsModelColumnRef('user', 'CommonClubsModelUser', 'userid', new CommonClubsControllerMappingRef('User', new CommonClubsModelFactoryUser())), + new CommonClubsModelColumnRef('club', 'CommonClubsModelClub', 'clubid', new CommonClubsControllerMappingRef('Club', new CommonClubsModelFactoryClub())), + new CommonClubsModelColumnRef('position', 'CommonClubsModelPosition', 'positionid', new CommonClubsControllerMappingRef('Position', new CommonClubsModelFactoryPosition())), + new CommonClubsModelColumnInt('admin', new CommonClubsControllerMappingInt('Admin')), + new CommonClubsModelColumnString('address', new CommonClubsControllerMappingString('Adresse', false)), + new CommonClubsModelColumnString('mail', new CommonClubsControllerMappingString('E-Mail', false)), + new CommonClubsModelColumnString('phone', new CommonClubsControllerMappingString('Telefonnummer', false)), + new CommonClubsModelColumnString('state', new CommonClubsControllerMappingString('Status')) // -> `state` enum('regular', 'vacant', 'temporary') NOT NULL DEFAULT 'vacant', ); } diff --git a/src/admin/views/user/tmpl/default.php b/src/admin/views/user/tmpl/default.php index 13467ec..e57da72 100644 --- a/src/admin/views/user/tmpl/default.php +++ b/src/admin/views/user/tmpl/default.php @@ -8,6 +8,7 @@ defined('_JEXEC') or die; ?> +

Stammdaten

@@ -55,5 +56,26 @@ defined('_JEXEC') or die;
+isNew): ?> +

Verknüpfungen

+ jobs) == 0): ?> +

Es wurden keine Verknüpfungen zu der Person gefunden.

+ + + + + + + + jobs as $job): ?> + + + + + + +
PostenVereinStadt
' target='_blank'>
+ +
'>Zurück zur Übersicht diff --git a/src/admin/views/user/view.html.php b/src/admin/views/user/view.html.php index 553b2e7..263da44 100644 --- a/src/admin/views/user/view.html.php +++ b/src/admin/views/user/view.html.php @@ -1,6 +1,7 @@ prepareDisplay(); + + if($this->isNew) + { + $this->jobs = array(); + } + else + { + $factory = new CommonClubsModelFactoryUser(); + $user = $factory->loadById($this->id); + + $positions = $user->getPositions(); + + $clubFactory = new CommonClubsModelFactoryClub(); + $clubsPresident = $clubFactory->loadElements("main.president = {$this->id}"); + + $jobs = array(); + + foreach($clubsPresident as $club) + { + $job = array( + 'club' => $club->getName(), + 'city' => $club->getCity(), + 'position' => 'Vorsitzender', + 'url' => Route::_("index.php?option=com_clubs&view=club&id={$club->getId()}") + ); + $jobs[] = $job; + } + foreach($positions as $p) + { + $c = $p->getClub(); + $job = array( + 'club' => $c->getName(), + 'city' => $c->getCity(), + 'position' => $p->getPosition()->getName(), + 'url' => Route::_("index.php?option=com_clubs&view=club&id={$c->getId()}") + ); + $jobs[] = $job; + } + $this->jobs = $jobs; + } + ToolbarHelper::title('Club-Management - Person', 'user'); parent::display($tpl); } diff --git a/src/admin/views/users/tmpl/default.php b/src/admin/views/users/tmpl/default.php index d03b8eb..066d170 100644 --- a/src/admin/views/users/tmpl/default.php +++ b/src/admin/views/users/tmpl/default.php @@ -1,6 +1,5 @@ Date: Tue, 11 Jun 2019 13:54:21 +0200 Subject: [PATCH 33/41] Offers can be changed in the backend --- src/admin/abstract/controller.php | 3 ++ src/admin/common/models/club.php | 42 ++++++++++++++++++++++++++- src/admin/controllers/club.php | 29 ++++++++---------- src/admin/views/club/tmpl/default.php | 3 +- 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/admin/abstract/controller.php b/src/admin/abstract/controller.php index 0856cbe..168197f 100644 --- a/src/admin/abstract/controller.php +++ b/src/admin/abstract/controller.php @@ -84,6 +84,7 @@ abstract class AbstractClubsController extends BaseController // Do the actual work $obj->save(); + $this->saveAssocs($obj, $values); // Redirect to the list of objects $view = $this->getSingleViewName(); @@ -105,6 +106,8 @@ abstract class AbstractClubsController extends BaseController } } + protected function saveAssocs($obj, $vlaues){} + protected function additionalData() { return array(); diff --git a/src/admin/common/models/club.php b/src/admin/common/models/club.php index a41e52b..37717da 100644 --- a/src/admin/common/models/club.php +++ b/src/admin/common/models/club.php @@ -61,9 +61,14 @@ class CommonClubsModelClub extends AbstractCommonClubsModel return $this->fetchAssociatedElements(new CommonClubsModelFactoryPlace(), 'clubid'); } + private function getOfferAssocs() + { + return $this->fetchAssociatedElements(new CommonClubsModelFactoryOfferassoc(), 'clubid'); + } + public function getOffers() { - $assocs = $this->fetchAssociatedElements(new CommonClubsModelFactoryOfferassoc(), 'clubid'); + $assocs = $this->getOfferAssocs(); $offersFactory = new CommonClubsModelFactoryOffer(); $allOffers = $offersFactory->loadElements(); @@ -92,6 +97,41 @@ class CommonClubsModelClub extends AbstractCommonClubsModel return $ret; } + /** + * @param int[] $ids + */ + public function setOfferIds($ids) + { + $dbo = $this->getFactory()->loadById($this->getId()); + $currentOffersAssocs = $dbo->getOfferAssocs(); + $currentIds = array_map(function($obj){ + return $obj->getOffer()->getId(); + }, $currentOffersAssocs); + + $newIds = array_diff($ids, $currentIds); + $delIds = array_diff($currentIds, $ids); + + $offerAssocFactory = new CommonClubsModelFactoryOfferassoc(); + $offerFactory = new CommonClubsModelFactoryOffer(); + + foreach($delIds as $id) + { + $delId = (int) $id; + $delObjs = $offerAssocFactory->loadElements("clubid = {$this->getId()} AND offerid = $delId"); + foreach($delObjs as $o) + $o->delete(); + } + + foreach($newIds as $id) + { + $newId = (int) $id; + $o = $offerAssocFactory->createNew(); + $o->setOffer($offerFactory->loadById($newId)); + $o->setClub($this); + $o->save(); + } + } + public function getUsers() { return $this->fetchAssociatedElements(new CommonClubsModelFactoryUserassoc(), 'clubid'); diff --git a/src/admin/controllers/club.php b/src/admin/controllers/club.php index b6114c3..75d2b8d 100644 --- a/src/admin/controllers/club.php +++ b/src/admin/controllers/club.php @@ -12,22 +12,6 @@ class ClubsControllerClub extends AbstractClubsController return 'club'; } - protected function getDataMapping() - { - return array( -// 'name' => array('required'=>true, 'name'=>'Bezeichnung', 'filter'=>'string') - 'name' => array('required'=>true, 'name'=>'Club-Name', 'filter'=>'string'), - 'address' => array('required'=>true, 'name'=>'Adresse', 'filter'=>'string'), - 'city' => array('required'=>true, 'name'=>'Stadt', 'filter'=>'string'), - 'homepage' => array('required'=>false, 'name'=>'Homepage', 'filter'=>'string'), - 'mail' => array('required'=>true, 'name'=>'E-Mail', 'filter'=>'string'), - 'iban' => array('required'=>true, 'name'=>'IBAN', 'filter'=>'string'), - 'bic' => array('required'=>true, 'name'=>'BIC', 'filter'=>'string'), - 'charitable' => array('skip_null_check'=>True), - 'president' => array('required'=>true, 'name'=>'Vorsitzender', 'skip_null_check'=>True, 'setter'=>'setPresidentId') - ); - } - protected function filterRaw(&$values) { if(is_null($values['charitable'])) @@ -46,6 +30,17 @@ class ClubsControllerClub extends AbstractClubsController { return new CommonClubsModelFactoryClub(); } + + protected function additionalData() + { + return array( + 'offers' => new CommonClubsControllerMappingInt('Angebot') + ); + } - + protected function saveAssocs($obj, $values) + { + $obj->setOfferIds($values['offers']); + } + } diff --git a/src/admin/views/club/tmpl/default.php b/src/admin/views/club/tmpl/default.php index e66f5b7..908952a 100644 --- a/src/admin/views/club/tmpl/default.php +++ b/src/admin/views/club/tmpl/default.php @@ -78,9 +78,10 @@ defined('_JEXEC') or die; isNew): ?>

Angebote

object->getOffers() as $oconf): ?> - getId(); ?>' > + getId(); ?>' > getName()); ?>
+

Posten

From 9cef15b4f5fa318f3b815aa8239d4107a78dd365 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Tue, 11 Jun 2019 14:11:46 +0200 Subject: [PATCH 34/41] Basic JS insertion successfully done --- src/admin/res/clubs.js | 8 ++++++++ src/admin/views/test/tmpl/default.php | 4 +++- src/admin/views/test/tmpl/foo.php | 2 ++ src/admin/views/test/view.html.php | 5 +++++ 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/admin/res/clubs.js diff --git a/src/admin/res/clubs.js b/src/admin/res/clubs.js new file mode 100644 index 0000000..be3e993 --- /dev/null +++ b/src/admin/res/clubs.js @@ -0,0 +1,8 @@ + +jQuery(function($){ + $('#mya').click(function() + { + //alert("a"); + console.log("a"); + }); +}); diff --git a/src/admin/views/test/tmpl/default.php b/src/admin/views/test/tmpl/default.php index 90b91bb..c114058 100644 --- a/src/admin/views/test/tmpl/default.php +++ b/src/admin/views/test/tmpl/default.php @@ -19,4 +19,6 @@ Place: getName(); ?>

Output

-
log); ?>
\ No newline at end of file +
log); ?>
+ +Test Link diff --git a/src/admin/views/test/tmpl/foo.php b/src/admin/views/test/tmpl/foo.php index 8b7e2d6..24670a8 100644 --- a/src/admin/views/test/tmpl/foo.php +++ b/src/admin/views/test/tmpl/foo.php @@ -4,3 +4,5 @@ defined('_JEXEC') or die; // Append &layout=foo to URL +?> +

This is foo.

diff --git a/src/admin/views/test/view.html.php b/src/admin/views/test/view.html.php index d6049fa..c080266 100644 --- a/src/admin/views/test/view.html.php +++ b/src/admin/views/test/view.html.php @@ -2,6 +2,7 @@ use Joomla\CMS\Toolbar\ToolbarHelper; use Joomla\CMS\MVC\View\HtmlView; +use Joomla\CMS\Factory; // No direct access. defined('_JEXEC') or die; @@ -44,6 +45,10 @@ class ClubsViewTest extends HtmlView $this->log = $np; parent::display($tpl); + //Factory::getDocument()->addScript( "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" ); + JHtmlJquery::framework(); + Factory::getDocument()->addScript('components/com_clubs/res/clubs.js'); + //jexit(); } protected function getViewName() From 150dfd8f60e1264d2c3e7b3ac5747192526a53ad Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Tue, 11 Jun 2019 14:12:37 +0200 Subject: [PATCH 35/41] Visual enhancement of club's presentation --- src/admin/views/club/tmpl/default.php | 50 +++++++++++++++------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/admin/views/club/tmpl/default.php b/src/admin/views/club/tmpl/default.php index 908952a..a521c47 100644 --- a/src/admin/views/club/tmpl/default.php +++ b/src/admin/views/club/tmpl/default.php @@ -83,29 +83,33 @@ defined('_JEXEC') or die;

Posten

-
- - - - - - - - - object->getUsers() as $ua): ?> - - - - - - - - - -
RolleNameStadtAdmin?ID
getPosition()->getName()); ?>getUser()->getName()); ?>getUser()->getCity()); ?>isAdmin()) echo ""; ?> - - - getId(); ?>
+object->getUsers()) == 0 ): ?> +

Dem Verein ist bisher kein Posten zugewiesen.

+ + + + + + + + + + + object->getUsers() as $ua): ?> + + + + + + + + + +
RolleNameStadtAdmin?ID
getPosition()->getName()); ?>getUser()->getName()); ?>getUser()->getCity()); ?>isAdmin()) echo ""; ?> + + + getId(); ?>
+

Neuen Posten einfügen

From 404ccf34981eae4ca6a0d3eeea5011138bad9616 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Tue, 11 Jun 2019 16:35:56 +0200 Subject: [PATCH 36/41] Basic dialog created without useful content yet --- src/admin/res/club/admin-club.css | 42 ++++++++++++++++++++++ src/admin/res/club/club.js | 34 ++++++++++++++++++ src/admin/views/club/tmpl/default.php | 10 +++--- src/admin/views/club/view.html.php | 7 ++++ src/admin/views/clubposition/view.html.php | 37 +++++++++++++++++++ 5 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 src/admin/res/club/admin-club.css create mode 100644 src/admin/res/club/club.js create mode 100644 src/admin/views/clubposition/view.html.php diff --git a/src/admin/res/club/admin-club.css b/src/admin/res/club/admin-club.css new file mode 100644 index 0000000..ecb4ce1 --- /dev/null +++ b/src/admin/res/club/admin-club.css @@ -0,0 +1,42 @@ + +#dialog-club { + width: 100vw; + height: 100vh; + position: fixed; + left: 0pt; + top: 0pt; + z-index: 1500; + /*border-style: solid;*/ +} + +#dialog-club > .dialog { + background-color: white; + width: 70vw; + height: 60vh; + position: absolute; + left: 15vw; + top: 20vh; + /*z-index: 100;*/ + border-style: solid; + /*visibility: visible;*/ + overflow: auto; +} + +#dialog-club > .background { + background-color: gray; + width: 100vw; + height: 100vh; + position: absolute; + left: 0pt; + top: 0pt; + /*z-index: 1000;*/ + opacity: 0.5; +} + +.dialog-hidden { + display: none; +} + +.form-disabled { + overflow: hidden; +} diff --git a/src/admin/res/club/club.js b/src/admin/res/club/club.js new file mode 100644 index 0000000..6faecdc --- /dev/null +++ b/src/admin/res/club/club.js @@ -0,0 +1,34 @@ + +jQuery(function($){ + + $('#new-position').click(function(ev){ + ev.preventDefault(); + + var url = $('#new-position').attr('href'); + $.get(url, function(data){ +// console.log(data); + $('#dialog-club > .dialog').html(data); + $('body').addClass('form-disabled'); + $('#dialog-club').removeClass('dialog-hidden'); + }); + + }); + + function closeDialog() { +// console.log('b'); + $('#dialog-club > .dialog').html(''); + $('body').removeClass('form-disabled'); + $('#dialog-club').addClass('dialog-hidden'); + } + + $(document).on('click', '#clubposition-abort', function(ev){ + ev.preventDefault(); +// console.log('a'); + closeDialog(); + }); + + $(document).on('click', '#clubposition-save', function(ev){ + ev.preventDefault(); + }); + +}); diff --git a/src/admin/views/club/tmpl/default.php b/src/admin/views/club/tmpl/default.php index a521c47..51f57df 100644 --- a/src/admin/views/club/tmpl/default.php +++ b/src/admin/views/club/tmpl/default.php @@ -7,7 +7,7 @@ defined('_JEXEC') or die; ?> -
+

Stammdaten

@@ -110,14 +110,14 @@ defined('_JEXEC') or die;
-

Neuen Posten einfügen

+

' id='new-position'> Neuen Posten einfügen


'>Zurück zur Übersicht
-
-
-
Ein Test
+
+
+
Ein Test
diff --git a/src/admin/views/club/view.html.php b/src/admin/views/club/view.html.php index 77934d5..82acea2 100644 --- a/src/admin/views/club/view.html.php +++ b/src/admin/views/club/view.html.php @@ -1,5 +1,6 @@ users = $userFactory->loadElements(); + JHtmlJquery::framework(); + Factory::getDocument()->addScript('components/com_clubs/res/club/club.js'); + Factory::getDocument()->addStyleSheet('components/com_clubs/res/club/admin-club.css'); + +// $this-> + parent::display($tpl); } diff --git a/src/admin/views/clubposition/view.html.php b/src/admin/views/clubposition/view.html.php new file mode 100644 index 0000000..3cc09cc --- /dev/null +++ b/src/admin/views/clubposition/view.html.php @@ -0,0 +1,37 @@ +positions = $positonFactory->loadElements(); + $this->users = $userFactory->loadElements(); + + $this->id = 'new'; + + parent::display($tpl); + + jexit(); + } + protected function getControllerName() + { + return 'position'; + } + + protected function getFactory() + { + return new CommonClubsModelFactoryPosition(); + } + +} From 7210dff28c46737ae2d5ef1f9413b9ec202f540a Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Tue, 11 Jun 2019 16:36:36 +0200 Subject: [PATCH 37/41] First interactions with controller using ajax done, nothing functional done yet. --- src/admin/controllers/clubposition.json.php | 19 ++++++ src/admin/res/club/admin-club.css | 5 ++ src/admin/res/club/club.js | 21 +++++- src/admin/views/clubposition/tmpl/new.php | 76 +++++++++++++++++++++ 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 src/admin/controllers/clubposition.json.php create mode 100644 src/admin/views/clubposition/tmpl/new.php diff --git a/src/admin/controllers/clubposition.json.php b/src/admin/controllers/clubposition.json.php new file mode 100644 index 0000000..dc904c8 --- /dev/null +++ b/src/admin/controllers/clubposition.json.php @@ -0,0 +1,19 @@ + .background { @@ -37,6 +38,10 @@ display: none; } +.dialog-entry-hidden { + visibility: hidden; +} + .form-disabled { overflow: hidden; } diff --git a/src/admin/res/club/club.js b/src/admin/res/club/club.js index 6faecdc..1b80421 100644 --- a/src/admin/res/club/club.js +++ b/src/admin/res/club/club.js @@ -15,7 +15,6 @@ jQuery(function($){ }); function closeDialog() { -// console.log('b'); $('#dialog-club > .dialog').html(''); $('body').removeClass('form-disabled'); $('#dialog-club').addClass('dialog-hidden'); @@ -23,12 +22,30 @@ jQuery(function($){ $(document).on('click', '#clubposition-abort', function(ev){ ev.preventDefault(); -// console.log('a'); closeDialog(); }); + $(document).on('change', '#clubposition-state', function(){ + if($('#clubposition-state').val() == "vacant") + $('#clubposition-user').addClass('dialog-entry-hidden'); + else + $('#clubposition-user').removeClass('dialog-entry-hidden'); + }); + $(document).on('click', '#clubposition-save', function(ev){ ev.preventDefault(); + //alert('Not yet implemented'); + + var data = $('#clubposition-form').serializeArray(); + + $.post($('#clubposition-form').attr('action'), data, function(data){ + console.log(data) + + if(data.success) + { + console.log("all right!"); + } + }); }); }); diff --git a/src/admin/views/clubposition/tmpl/new.php b/src/admin/views/clubposition/tmpl/new.php new file mode 100644 index 0000000..61bfe03 --- /dev/null +++ b/src/admin/views/clubposition/tmpl/new.php @@ -0,0 +1,76 @@ + + +
+ + +

Posten bearbeiten

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Person: + +
Funktion: + +
Status + +
Admin: + +
Adresse (optional): + +
E-Mail (optional): + +
Telefon-Nr. (optional): + +
+ +
+ +Speichern +Abbrechen + From 28494f6142212de186e121af9efb8e6a9dbc6f52 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Wed, 12 Jun 2019 18:15:06 +0200 Subject: [PATCH 38/41] User assiciations are changable from within the backend interface from the clubs. --- src/admin/controllers/clubposition.json.php | 74 ++++++++++++++++- src/admin/res/club/admin-club.css | 4 + src/admin/res/club/club.js | 81 +++++++++++++++---- src/admin/views/club/tmpl/default.php | 30 +++++-- src/admin/views/clubposition/tmpl/edit.php | 90 +++++++++++++++++++++ src/admin/views/clubposition/tmpl/new.php | 76 ----------------- src/admin/views/clubposition/tmpl/row.php | 17 ++++ src/admin/views/clubposition/view.html.php | 29 ++++++- 8 files changed, 299 insertions(+), 102 deletions(-) create mode 100644 src/admin/views/clubposition/tmpl/edit.php delete mode 100644 src/admin/views/clubposition/tmpl/new.php create mode 100644 src/admin/views/clubposition/tmpl/row.php diff --git a/src/admin/controllers/clubposition.json.php b/src/admin/controllers/clubposition.json.php index dc904c8..b946e53 100644 --- a/src/admin/controllers/clubposition.json.php +++ b/src/admin/controllers/clubposition.json.php @@ -2,6 +2,7 @@ use Joomla\CMS\MVC\Controller\BaseController; use Joomla\CMS\Response\JsonResponse; +use Joomla\CMS\Factory; // No direct access. defined('_JEXEC') or die; @@ -11,9 +12,80 @@ class ClubsControllerClubposition extends BaseController public function save() { - echo new JsonResponse('abc', null, false, true); + $app = Factory::getApplication(); + $input = $app->input->post; + + $id = $input->getCmd('id', 'new'); + $clubid = $input->getInt('clubid'); + $positionid = $input->getInt('positionid'); + $state = $input->getCmd('state', 'regular'); + $admin = $input->getCmd('admin', '0'); + $userid = $input->getInt('userid', -1); + $address = $input->getString('address'); + $mail = $input->getString('mail'); + $phone = $input->getString('phone'); + + $clubFactory = new CommonClubsModelFactoryClub(); + $club = $clubFactory->loadById($clubid); + $positionFactory = new CommonClubsModelFactoryPosition(); + $position = $positionFactory->loadById($positionid); + + $assocFactory = new CommonClubsModelFactoryUserassoc(); + + if($id === 'new') + { + $assoc = $assocFactory->createNew(); + } + else + { + $assoc = $assocFactory->loadById((int) $id); + } + + if($userid != -1) + { + $userFactory = new CommonClubsModelFactoryUser(); + $user = $userFactory->loadById($userid); + } + else + { + $user = null; + } + + if($state === 'vacant') + $user = null; + + $assoc->setUser($user); + + $assoc->setState($state); + $assoc->setAddress($address === '' ? null : $address); + $assoc->setMail($mail === '' ? null : $mail); + $assoc->setPhone($phone === '' ? null : $phone); + $assoc->setClub($club); + $assoc->setAdmin($admin); + $assoc->setPosition($position); + + $assoc->save(); + + $ret = array('new' => ($id === 'new'), 'id' => $assoc->getId()); + + echo new JsonResponse($ret, null, false, true); // jexit(); } + public function delete() + { + $app = Factory::getApplication(); + $input = $app->input->get; + + $id = $input->getInt('id'); + $factory = new CommonClubsModelFactoryUserassoc(); + $ua = $factory->loadById($id); + $ua->delete(); + + $ret = array('id' => $id); + + echo new JsonResponse($ret, null, false, true); + } + } diff --git a/src/admin/res/club/admin-club.css b/src/admin/res/club/admin-club.css index b34409d..5ed57ba 100644 --- a/src/admin/res/club/admin-club.css +++ b/src/admin/res/club/admin-club.css @@ -45,3 +45,7 @@ .form-disabled { overflow: hidden; } + +#hidden-id { + display: none; +} diff --git a/src/admin/res/club/club.js b/src/admin/res/club/club.js index 1b80421..ca4b89f 100644 --- a/src/admin/res/club/club.js +++ b/src/admin/res/club/club.js @@ -1,25 +1,29 @@ jQuery(function($){ - $('#new-position').click(function(ev){ - ev.preventDefault(); - - var url = $('#new-position').attr('href'); - $.get(url, function(data){ -// console.log(data); - $('#dialog-club > .dialog').html(data); - $('body').addClass('form-disabled'); - $('#dialog-club').removeClass('dialog-hidden'); - }); - - }); - function closeDialog() { $('#dialog-club > .dialog').html(''); $('body').removeClass('form-disabled'); $('#dialog-club').addClass('dialog-hidden'); } + function openDialog(data) { +// console.log(data); + $('#dialog-club > .dialog').html(data); + $('body').addClass('form-disabled'); + $('#dialog-club').removeClass('dialog-hidden'); + } + + $('#new-position').click(function(ev){ + ev.preventDefault(); + + var url = $('#new-position').attr('href'); + $.get(url, function(data){ + openDialog(data); + }); + + }); + $(document).on('click', '#clubposition-abort', function(ev){ ev.preventDefault(); closeDialog(); @@ -34,18 +38,65 @@ jQuery(function($){ $(document).on('click', '#clubposition-save', function(ev){ ev.preventDefault(); - //alert('Not yet implemented'); var data = $('#clubposition-form').serializeArray(); $.post($('#clubposition-form').attr('action'), data, function(data){ - console.log(data) +// console.log(data) if(data.success) { console.log("all right!"); + + var url = "index.php?option=com_clubs&view=clubposition&layout=row&id=" + data.data.id + "&club=" + $('#hidden-id').html(); + console.log(url); + + if(data.data.new) { + $.get(url, function(data2){ + $("#userassocs").append(data2); + closeDialog(); + }); + } else { + $.get(url, function(data2){ + $('#userassoc-' + data.data.id).html(data2); + closeDialog(); + }); + } + } + else + { + alert(data.message); } }); }); + $(document).on('click', '.edit-position', function(ev){ + ev.preventDefault(); + + var url = ev.currentTarget.href; + + $.get(url, function(data){ + openDialog(data); + }); + }); + + $(document).on('click', '.del-position', function(ev){ + ev.preventDefault(); + + if(confirm("Der Eintrag wird endgĂĽltig gelöscht werden. OK?")) { + var url = ev.currentTarget.href; + $.get(url, function(d){ + if(d.success) { + $('#userassoc-' + d.data.id).remove(); + } + }); + } + + }); + + $(window).on('beforeunload', function(){return "Wollen Sie die Seite wirklich verlassen? Möglicherweise sind nicht alle Daten gesichert.";}); + $('.form-exit').click(function(){ + $(window).off('beforeunload'); + }); + }); diff --git a/src/admin/views/club/tmpl/default.php b/src/admin/views/club/tmpl/default.php index 51f57df..480d21d 100644 --- a/src/admin/views/club/tmpl/default.php +++ b/src/admin/views/club/tmpl/default.php @@ -86,7 +86,7 @@ defined('_JEXEC') or die; object->getUsers()) == 0 ): ?>

Dem Verein ist bisher kein Posten zugewiesen.

- +
@@ -96,28 +96,42 @@ defined('_JEXEC') or die; object->getUsers() as $ua): ?> - + getUser(); + if($user == null) + { + $username = 'Derzeit vakant'; + $usercity = ''; + } + else + { + $username = htmlentities($user->getName()); + $usercity = htmlentities($user->getCity()); + } + ?> + - - + +
Rolle NameID
getPosition()->getName()); ?>getUser()->getName()); ?>getUser()->getCity()); ?> isAdmin()) echo ""; ?> - - + + getId(); ?>
-

' id='new-position'> Neuen Posten einfügen

+

Neuen Posten einfügen

-
'>Zurück zur Übersicht +
' class='form-exit'>Zurück zur Übersicht
Ein Test
+
object->getId(); ?>
diff --git a/src/admin/views/clubposition/tmpl/edit.php b/src/admin/views/clubposition/tmpl/edit.php new file mode 100644 index 0000000..877851e --- /dev/null +++ b/src/admin/views/clubposition/tmpl/edit.php @@ -0,0 +1,90 @@ + + +
+ + +

Posten bearbeiten

+ + + + + + + + + + + assoc->getState() === 'vacant') echo 'class="dialog-entry-hidden"'; ?>> + + + + + + + + + + + + + + + + + + + +
Funktion: + +
Status + +
Person: + +
Admin: + assoc->isAdmin() ? 'checked' : ''; ?>> +
Adresse (optional): + +
E-Mail (optional): + +
Telefon-Nr. (optional): + +
+ +
+ +Speichern +Abbrechen + diff --git a/src/admin/views/clubposition/tmpl/new.php b/src/admin/views/clubposition/tmpl/new.php deleted file mode 100644 index 61bfe03..0000000 --- a/src/admin/views/clubposition/tmpl/new.php +++ /dev/null @@ -1,76 +0,0 @@ - - -
- - -

Posten bearbeiten

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Person: - -
Funktion: - -
Status - -
Admin: - -
Adresse (optional): - -
E-Mail (optional): - -
Telefon-Nr. (optional): - -
- -
- -Speichern -Abbrechen - diff --git a/src/admin/views/clubposition/tmpl/row.php b/src/admin/views/clubposition/tmpl/row.php new file mode 100644 index 0000000..8f1543f --- /dev/null +++ b/src/admin/views/clubposition/tmpl/row.php @@ -0,0 +1,17 @@ + +assoc->getPosition()->getName()); ?> +username; ?> +usercity; ?> +assoc->isAdmin()) echo ""; ?> + + + + +assoc->getId(); ?> diff --git a/src/admin/views/clubposition/view.html.php b/src/admin/views/clubposition/view.html.php index 3cc09cc..502125d 100644 --- a/src/admin/views/clubposition/view.html.php +++ b/src/admin/views/clubposition/view.html.php @@ -1,6 +1,7 @@ input->get; $positonFactory = new CommonClubsModelFactoryPosition(); $userFactory = new CommonClubsModelFactoryUser(); $this->positions = $positonFactory->loadElements(); $this->users = $userFactory->loadElements(); - $this->id = 'new'; + $id = $input->getCmd('id', 'new'); + $assocFactory = new CommonClubsModelFactoryUserassoc(); + + if($id !== 'new') + { + $id = (int) $id; + + $this->assoc = $assocFactory->loadById($id); + } + else + { + $this->assoc = $assocFactory->createNew(); + } + + $this->id = $id; + $this->clubid = $input->getInt('club'); + + $this->username = 'Derzeit vakant.'; + $this->usercity = ''; + if($this->assoc->getUser() !== null) + { + $u = $this->assoc->getUser(); + $this->username = htmlentities($u->getName()); + $this->usercity = htmlentities($u->getCity()); + } parent::display($tpl); From c95b3c31a6e5a38913138032b33ff9046971424a Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 13 Jun 2019 11:02:39 +0200 Subject: [PATCH 39/41] Preliminary preparations for deployment are set up --- .gitignore | 3 + Makefile | 47 +++++++++++++++ releases/.dummy | 0 releases/clubs-0.0.1.tar.gz | Bin 0 -> 28765 bytes res/postfix.template | 1 + res/prefix.template | 2 + res/update.template | 16 ++++++ scripts/create-release.sh | 23 ++++++++ scripts/create-update-xml.sh | 23 ++++++++ serial/major | 1 + serial/minor | 1 + serial/release | 1 + src/admin/sql/mysql/install.sql | 80 ++++++++++++++++++++++++++ src/admin/sql/mysql/uninstall.sql | 9 +++ src/admin/sql/mysql/updates/0.0.1.sql | 0 src/admin/sql/updates/mysql/0.0.1.sql | 18 ------ src/clubs.xml | 15 ++++- 17 files changed, 219 insertions(+), 21 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 releases/.dummy create mode 100644 releases/clubs-0.0.1.tar.gz create mode 100644 res/postfix.template create mode 100644 res/prefix.template create mode 100644 res/update.template create mode 100755 scripts/create-release.sh create mode 100755 scripts/create-update-xml.sh create mode 100644 serial/major create mode 100644 serial/minor create mode 100644 serial/release create mode 100644 src/admin/sql/mysql/install.sql create mode 100644 src/admin/sql/mysql/uninstall.sql create mode 100644 src/admin/sql/mysql/updates/0.0.1.sql delete mode 100644 src/admin/sql/updates/mysql/0.0.1.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..751b666 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/clubs.tar.gz +/slt-update.xml + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..327d4c9 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ + +# UPDATE_PATH=/srv/slt-update +PACKAGES = clubs.tar.gz + +export DL_PREFIX=https://slt.wolf-stuttgart.net/update/com_clubs + +RSYNC_OPTIONS=-rltDz --delete --delete-delay --delete-excluded --exclude '.gitignore' --exclude '.dummy' +RSYNC_USER=christian +RSYNC_HOST=hh.wolf-stuttgart.net +DL_PATH=/srv/http/slt/update/com_clubs + +all: package + +package: $(PACKAGES) + +.PHONY: clubs.tar.gz +clubs.tar.gz: + tar czf $@ -C src . + +.PHONY: release +release: clubs.tar.gz + ./scripts/create-release.sh + +.PHONY: release-force +release-force: clubs.tar.gz + ./scripts/create-release.sh -f + +.PHONY: upload +upload: release + $(MAKE) reupload + +.PHONY: slt-update.xml +slt-update.xml: + ./scripts/create-update-xml.sh + +.PHONY: reupload +reupload: slt-update.xml + @echo Pushing files + @rsync $(RSYNC_OPTIONS) releases/ $(RSYNC_USER)@$(RSYNC_HOST):$(DL_PATH)/files -v + + @echo Pushing XML configuration + @rsync $(RSYNC_OPTIONS) slt-update.xml $(RSYNC_USER)@$(RSYNC_HOST):$(DL_PATH) + +# +# .PHONY: update +# update: $(PACKAGES) +# cp slt.xml slt1.tar.gz $(UPDATE_PATH) diff --git a/releases/.dummy b/releases/.dummy new file mode 100644 index 0000000..e69de29 diff --git a/releases/clubs-0.0.1.tar.gz b/releases/clubs-0.0.1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..7adda59a6bbd79e6b2fa78f27a2291dac05ed807 GIT binary patch literal 28765 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

Rd>ja~B^-g+n= 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 literal 0 HcmV?d00001 diff --git a/res/postfix.template b/res/postfix.template new file mode 100644 index 0000000..769fb5f --- /dev/null +++ b/res/postfix.template @@ -0,0 +1 @@ + diff --git a/res/prefix.template b/res/prefix.template new file mode 100644 index 0000000..ab72107 --- /dev/null +++ b/res/prefix.template @@ -0,0 +1,2 @@ + + diff --git a/res/update.template b/res/update.template new file mode 100644 index 0000000..007f149 --- /dev/null +++ b/res/update.template @@ -0,0 +1,16 @@ + + SLT Vereinsverwaltung + Vereinsportal des saarländischen Tanzsportverbands + com_clubs + component + 0 + __VERSION__ + + __DOWNLOAD_URL__ + + + stable + + Christian Wolf + + diff --git a/scripts/create-release.sh b/scripts/create-release.sh new file mode 100755 index 0000000..122d8a9 --- /dev/null +++ b/scripts/create-release.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +MAJOR=`cat serial/major` +MINOR=`cat serial/minor` +RELEASE=`cat serial/release` +TOTRELEASE="$MAJOR.$MINOR.$RELEASE" + +outname="releases/clubs-$TOTRELEASE.tar.gz" + +mkdir -p releases + +# First check if the named release exists already +if [ -f "$outname" ]; then + if [ "$1" = '-f' ]; then + echo "Overwriting existing file $outname." + else + echo "The file $outname existes already. Either remove the release from the folder manually or increase the release version." + echo "Aborting." + exit 1 + fi +fi + +cp clubs.tar.gz "$outname" diff --git a/scripts/create-update-xml.sh b/scripts/create-update-xml.sh new file mode 100755 index 0000000..42ad43a --- /dev/null +++ b/scripts/create-update-xml.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# DL_PREFIX="https://slt.wolf-stuttgart.net/update/template" + +cat res/prefix.template > slt-update.xml + +find releases -name clubs-\* | while read f +do + + VSTR=`echo "$f" | sed -E 's@releases/clubs-@@; s@^([0-9]+\.[0-9]+\.[0-9]+).*$@\1@'` + + FILE=`echo "$f" | sed 's@releases/@@'` + FORMAT=`echo "$f" | sed -E 's@^.*[0-9]+\.[0-9]+\.[0-9]+\.@@'` + + sed ' + s@__VERSION__@'"$VSTR"'@g; + s@__DOWNLOAD_URL__@'"$DL_PREFIX/files/$FILE"'@g; + s@__FORMAT__@'"$FORMAT"'@g + ' res/update.template >> slt-update.xml + +done + +cat res/postfix.template >> slt-update.xml diff --git a/serial/major b/serial/major new file mode 100644 index 0000000..573541a --- /dev/null +++ b/serial/major @@ -0,0 +1 @@ +0 diff --git a/serial/minor b/serial/minor new file mode 100644 index 0000000..573541a --- /dev/null +++ b/serial/minor @@ -0,0 +1 @@ +0 diff --git a/serial/release b/serial/release new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/serial/release @@ -0,0 +1 @@ +1 diff --git a/src/admin/sql/mysql/install.sql b/src/admin/sql/mysql/install.sql new file mode 100644 index 0000000..c58928a --- /dev/null +++ b/src/admin/sql/mysql/install.sql @@ -0,0 +1,80 @@ + +DROP TABLE IF EXISTS `#__club_clubs`; +CREATE TABLE `#__club_clubs` ( + `id` int(10) NOT NULL AUTO_INCREMENT, + `name` varchar(100) NOT NULL, + `address` tinytext NOT NULL, + `city` varchar(50) NOT NULL, + `homepage` varchar(100) DEFAULT NULL, + `mail` varchar(100) NOT NULL, + `iban` char(34) NOT NULL, + `bic` char(11) NOT NULL, + `charitable` tinyint(1) NOT NULL, + `president` int(10) NOT NULL, + PRIMARY KEY (`id`) +); + +DROP TABLE IF EXISTS `#__club_keys`; +CREATE TABLE `#__club_keys` ( + `privkey` text NOT NULL, + `publickey` text NOT NULL +); + +DROP TABLE IF EXISTS `#__club_offer_assocs`; +CREATE TABLE `#__club_offer_assocs` ( + `id` int(10) NOT NULL AUTO_INCREMENT, + `clubid` int(10) NOT NULL, + `offerid` int(10) NOT NULL, + PRIMARY KEY (`id`) +); + +DROP TABLE IF EXISTS `#__club_offers`; +CREATE TABLE `#__club_offers` ( + `id` int(10) NOT NULL AUTO_INCREMENT, + `name` varchar(100) NOT NULL, + PRIMARY KEY (`id`) +); + +DROP TABLE IF EXISTS `#__club_places`; +CREATE TABLE `#__club_places` ( + `id` int(10) NOT NULL AUTO_INCREMENT, + `clubid` int(10) NOT NULL, + `name` varchar(100) NOT NULL, + `address` tinytext NOT NULL, + `area` int(10) DEFAULT NULL, + PRIMARY KEY (`id`) +); + +DROP TABLE IF EXISTS `#__club_positions`; +CREATE TABLE `#__club_positions` ( + `id` int(10) NOT NULL AUTO_INCREMENT, + `name` varchar(100) NOT NULL, + PRIMARY KEY (`id`) + +DROP TABLE IF EXISTS `#__club_user_assocs`; +CREATE TABLE `#__club_user_assocs` ( + `id` int(10) NOT NULL AUTO_INCREMENT, + `clubid` int(10) NOT NULL, + `userid` int(10) DEFAULT NULL, + `positionid` int(10) NOT NULL, + `admin` tinyint(1) NOT NULL DEFAULT 0, + `state` enum('regular','vacant','temporary') NOT NULL DEFAULT 'vacant', + `address` tinytext DEFAULT NULL, + `mail` varchar(100) DEFAULT NULL, + `phone` varchar(50) DEFAULT NULL, + PRIMARY KEY (`id`) +); + +DROP TABLE IF EXISTS `#__club_users`; +CREATE TABLE `#__club_users` ( + `id` int(10) NOT NULL AUTO_INCREMENT, + `user` varchar(30) NOT NULL, + `password` varchar(150) DEFAULT NULL, + `name` varchar(255) NOT NULL, + `address` tinytext NOT NULL, + `city` varchar(50) NOT NULL, + `mail` varchar(100) NOT NULL, + `phone` varchar(50) DEFAULT NULL, + `mobile` varchar(50) DEFAULT NULL, + PRIMARY KEY (`id`) +); diff --git a/src/admin/sql/mysql/uninstall.sql b/src/admin/sql/mysql/uninstall.sql new file mode 100644 index 0000000..61d1555 --- /dev/null +++ b/src/admin/sql/mysql/uninstall.sql @@ -0,0 +1,9 @@ + +DROP TABLE IF EXISTS `#__club_clubs`; +DROP TABLE IF EXISTS `#__club_keys`; +DROP TABLE IF EXISTS `#__club_offer_assocs`; +DROP TABLE IF EXISTS `#__club_offers`; +DROP TABLE IF EXISTS `#__club_places`; +DROP TABLE IF EXISTS `#__club_positions`; +DROP TABLE IF EXISTS `#__club_user_assocs`; +DROP TABLE IF EXISTS `#__club_users`; diff --git a/src/admin/sql/mysql/updates/0.0.1.sql b/src/admin/sql/mysql/updates/0.0.1.sql new file mode 100644 index 0000000..e69de29 diff --git a/src/admin/sql/updates/mysql/0.0.1.sql b/src/admin/sql/updates/mysql/0.0.1.sql deleted file mode 100644 index fd82b06..0000000 --- a/src/admin/sql/updates/mysql/0.0.1.sql +++ /dev/null @@ -1,18 +0,0 @@ -DROP TABLE IF EXISTS `#__club_keys`; -CREATE TABLE `#__club_keys` ( - `privkey` text NOT NULL, - `publickey` text NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - -INSERT INTO `#__club_keys` VALUES ('-----BEGIN PRIVATE KEY-----\nMIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDvZHhLHHqmfnnP\noj5Y7AtS0UvEN8XLzNdHj/0nHBVHrXDMNul8phbCaevuaXcYTZvb9JBTAO019Bnu\nCoj6KFrk0Cw/zcVgVHryguNCr2DeLaxAXyArfkKNZPuG/kTw1mxE6ebeR886JVU0\nfQpIi3zDGAHpXxaKO7EttnvQK8NRd4h5O5EI6dCnmMn/8xY/d5G7AsZKuAxq01ZB\nmmUrkRKrQtBTwUeJWhtfT+XjOa1n1FVIwmqz4O2sqbfm0pa6PX0BNT0CUp0RI1wN\nGmF6bh/WkQ8h3jmTamzZ2cQUwDnL7ByAJqexJrrPacRbE6EotCGODjuBXDSy02PJ\nZk0fThZpAgMBAAECggEBANJaU0WU8cUo73pekzBpwY11MYFqjJiHF0ffo0/hYmWI\nZsxbGBGak/cjQdhNvgOR9nlxTfxRnR7CrqI3iaNfIHdc4PTzqBL8SMid8HohRxoT\nwf8SV19AY9SAvnAhL4z8rUhky6PYL6RlN5iWoxmZz/hSOSPKlTDEQOztI6CJQ+hp\nUFEiOLq/TSWB1DYEu6qAQRyI5wcC1QMo+zB/zBwIuM3DsB2f6Xtj86kXzU/bGeDj\nNLbsCuhRbFyMgvcJ3z6KQTvREZOGkeF22HA+ZVH7bWf00PqCEjq2I/XFHZGK61X4\nWLgNOUmVZSmGv2Kd9UJaC8VffkBnrV3XFN1n97gcP4ECgYEA/Joaw3VrbWpKsquf\nMiTuMJVyvcV9fmVhWhPNuioCwWwBx2mjGz/texd/67KNWPr7r1IGCIBnYfs3Dy+k\nOo+lAWT7oaec24dH4vjiljWrvFvZXmkpTa6j8RSqhhIVTUFSnXBkvxkEzcnUMbxK\n6A4gdggSfcMpeo3DmjGqNWTXlbkCgYEA8pzgD2VFEYq4/fgDhxKeoWSt1UPJtPxT\n4iYPXWKGdWLsEKg36oYiRXSTdan5aRwiZUXpDoFJU8vVCfmdVb2qpwPp66LwYFax\nAYuA3aPTi9npT4nDOBygVGSTY0TsxmL5asndyxl99OdoskLmS3N2UzeWMy7prRTt\nWZL7uWnU3jECgYEA+8EHLYkIcXs/SYV823gtIKTCP9rlsSSPezxDjOgz6NyhhUKG\nejEjmcuZBarl7ynt0BU6yBxZbTD39h2wW2EwvgwMMlggIDda8R7GjZieOdN89/ht\nXuvQnwTO02KQcnfJQ/pxnnfr7sHZx1v3eAIkXh67dzYcT/WcXkBjeGWx65kCgYEA\n0WU8rY0GU+GbUPfic45Kg5nVvx/G1AKEk8etszf6PQ6oGKhXun8SzUeUjhWqQmIZ\n/badM/u6QizHLtjWRs5wn73tA4eaWEv6cg7ppmJjj20AxbltWNy8NJqp6x/uDDwY\ncWbRmx3hbKLd5h1/jteU5LYxWPPAk7ZgNNN7l8gUBcECgYEAzVVKQ5dn58iHkH1i\nB3UUnTrqPD/cn7WigcWn7A6vpxqbGARkAyIXV/xqLoD8pX1G7JhqFKArC+UJzUuC\n6hgL1Hq+OJXTaN2NzcvQaIvLzsOWQebFtNfFxi0tzAE0t2/JfuFxeam0WZunaNgg\nAabygognDdEZW9HBvCydJ5N8e+s=\n-----END PRIVATE KEY-----\n','-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA72R4Sxx6pn55z6I+WOwL\nUtFLxDfFy8zXR4/9JxwVR61wzDbpfKYWwmnr7ml3GE2b2/SQUwDtNfQZ7gqI+iha\n5NAsP83FYFR68oLjQq9g3i2sQF8gK35CjWT7hv5E8NZsROnm3kfPOiVVNH0KSIt8\nwxgB6V8WijuxLbZ70CvDUXeIeTuRCOnQp5jJ//MWP3eRuwLGSrgMatNWQZplK5ES\nq0LQU8FHiVobX0/l4zmtZ9RVSMJqs+DtrKm35tKWuj19ATU9AlKdESNcDRphem4f\n1pEPId45k2ps2dnEFMA5y+wcgCansSa6z2nEWxOhKLQhjg47gVw0stNjyWZNH04W\naQIDAQAB\n-----END PUBLIC KEY-----\n'); - - -DROP TABLE IF EXISTS `#__club_users`; -CREATE TABLE `#__club_users` ( - `id` int(10) NOT NULL AUTO_INCREMENT, - `user` varchar(30) NOT NULL, - `password` varchar(150) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; - -INSERT INTO `#__club_users` VALUES (1,'chris','$2y$10$2/aeOfLlLwFx7JYXpaAOH.CG0ZtvkzTuK6nojvSIuOYlTbSd07CqC'); diff --git a/src/clubs.xml b/src/clubs.xml index 518fc17..84217ba 100644 --- a/src/clubs.xml +++ b/src/clubs.xml @@ -5,7 +5,7 @@ type="component"> - Vereinsmanagement + SLT Vereinsmanagement Vereinsportal des saarländischen Tanzsportverbands @@ -46,11 +46,20 @@

Vereinsmanagement + Vereine + Personen + Posten + Trainingsangebote + abstract + common + controllers + res + sql + views clubs.php controller.php - sql @@ -66,7 +75,7 @@ - sql/updates/mysql + sql/mysql/updates From 44e90e2adb2b59da1e8e12b38a16928bb83db6b3 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 13 Jun 2019 13:32:53 +0200 Subject: [PATCH 40/41] Installable version created --- src/admin/sql/mysql/install.sql | 50 +++++++++++++-------------------- src/clubs.xml | 23 ++++++++------- 2 files changed, 32 insertions(+), 41 deletions(-) diff --git a/src/admin/sql/mysql/install.sql b/src/admin/sql/mysql/install.sql index c58928a..076c280 100644 --- a/src/admin/sql/mysql/install.sql +++ b/src/admin/sql/mysql/install.sql @@ -1,7 +1,6 @@ -DROP TABLE IF EXISTS `#__club_clubs`; -CREATE TABLE `#__club_clubs` ( - `id` int(10) NOT NULL AUTO_INCREMENT, +CREATE TABLE IF NOT EXISTS `#__club_clubs` ( + `id` int(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` varchar(100) NOT NULL, `address` tinytext NOT NULL, `city` varchar(50) NOT NULL, @@ -10,49 +9,39 @@ CREATE TABLE `#__club_clubs` ( `iban` char(34) NOT NULL, `bic` char(11) NOT NULL, `charitable` tinyint(1) NOT NULL, - `president` int(10) NOT NULL, - PRIMARY KEY (`id`) + `president` int(10) NOT NULL ); -DROP TABLE IF EXISTS `#__club_keys`; -CREATE TABLE `#__club_keys` ( +CREATE TABLE IF NOT EXISTS `#__club_keys` ( `privkey` text NOT NULL, `publickey` text NOT NULL ); -DROP TABLE IF EXISTS `#__club_offer_assocs`; -CREATE TABLE `#__club_offer_assocs` ( - `id` int(10) NOT NULL AUTO_INCREMENT, +CREATE TABLE IF NOT EXISTS `#__club_offer_assocs` ( + `id` int(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, `clubid` int(10) NOT NULL, - `offerid` int(10) NOT NULL, - PRIMARY KEY (`id`) + `offerid` int(10) NOT NULL ); -DROP TABLE IF EXISTS `#__club_offers`; -CREATE TABLE `#__club_offers` ( - `id` int(10) NOT NULL AUTO_INCREMENT, - `name` varchar(100) NOT NULL, - PRIMARY KEY (`id`) +CREATE TABLE IF NOT EXISTS `#__club_offers` ( + `id` int(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `name` varchar(100) NOT NULL ); -DROP TABLE IF EXISTS `#__club_places`; -CREATE TABLE `#__club_places` ( - `id` int(10) NOT NULL AUTO_INCREMENT, +CREATE TABLE IF NOT EXISTS `#__club_places` ( + `id` int(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, `clubid` int(10) NOT NULL, `name` varchar(100) NOT NULL, `address` tinytext NOT NULL, - `area` int(10) DEFAULT NULL, - PRIMARY KEY (`id`) + `area` int(10) DEFAULT NULL ); -DROP TABLE IF EXISTS `#__club_positions`; -CREATE TABLE `#__club_positions` ( - `id` int(10) NOT NULL AUTO_INCREMENT, - `name` varchar(100) NOT NULL, - PRIMARY KEY (`id`) +CREATE TABLE IF NOT EXISTS `#__club_positions` ( + `id` int(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, + `name` varchar(100) NOT NULL +); -DROP TABLE IF EXISTS `#__club_user_assocs`; -CREATE TABLE `#__club_user_assocs` ( +CREATE TABLE IF NOT EXISTS `#__club_user_assocs` ( `id` int(10) NOT NULL AUTO_INCREMENT, `clubid` int(10) NOT NULL, `userid` int(10) DEFAULT NULL, @@ -65,8 +54,7 @@ CREATE TABLE `#__club_user_assocs` ( PRIMARY KEY (`id`) ); -DROP TABLE IF EXISTS `#__club_users`; -CREATE TABLE `#__club_users` ( +CREATE TABLE IF NOT EXISTS `#__club_users` ( `id` int(10) NOT NULL AUTO_INCREMENT, `user` varchar(30) NOT NULL, `password` varchar(150) DEFAULT NULL, diff --git a/src/clubs.xml b/src/clubs.xml index 84217ba..fb153e5 100644 --- a/src/clubs.xml +++ b/src/clubs.xml @@ -5,8 +5,9 @@ type="component"> - SLT Vereinsmanagement + Vereinsportal Vereinsportal des saarländischen Tanzsportverbands + com_clubs 30.03.2019 @@ -25,11 +26,13 @@ - + clubs.php controller.php - controller + controllers + css helpers + js models views @@ -46,12 +49,12 @@ Vereinsmanagement - Vereine - Personen - Posten - Trainingsangebote + Vereine + Personen + Posten + Trainingsangebote - + abstract common controllers @@ -65,12 +68,12 @@ - sql/mysql/install.sql + sql/mysql/install.sql - sql/mysql/uninstall.sql + sql/mysql/uninstall.sql From b96506c8aead728c02d515946763806f121c7f5e Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 13 Jun 2019 14:39:49 +0200 Subject: [PATCH 41/41] Updated minor modifications --- releases/clubs-0.0.1.tar.gz | Bin 28765 -> 28715 bytes src/clubs.xml | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/releases/clubs-0.0.1.tar.gz b/releases/clubs-0.0.1.tar.gz index 7adda59a6bbd79e6b2fa78f27a2291dac05ed807..c5c6b8c93a3d276dd652b9bd01f7f1953dcbf0bf 100644 GIT binary patch delta 22257 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%0ALE2ZQKOGfXbujxlYDPsRCBDje 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 delta 22293 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 diff --git a/src/clubs.xml b/src/clubs.xml index fb153e5..1351157 100644 --- a/src/clubs.xml +++ b/src/clubs.xml @@ -49,10 +49,10 @@ Vereinsmanagement - Vereine - Personen Posten Trainingsangebote + Personen + Vereine abstract