diff --git a/.gitmodules b/.gitmodules index 9383a89..60d40bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "misc/controller/ethersex"] path = misc/controller/ethersex url = git://github.com/ethersex/ethersex.git +[submodule "misc/website"] + path = misc/website + url = git@github.com:volkszaehler/website.git diff --git a/README b/README index dbdf8d1..8422b06 100644 --- a/README +++ b/README @@ -18,7 +18,8 @@ volkszaehler.org/ | \_ demo/ Demo Daten zum Import in die Datenbank | |_ tools/ Verschiedene Scripts zum Importieren von Daten, Installation, Helper etc. - \_ tests/ Einfache Testcases für einige Klasses des Backends + |_ tests/ Einfache Testcases für einige Klasses des Backends + \_ website/ Die Website unter http://volkszaehler.org Wiki: http://wiki.volkszaehler.org Other: http://volkszaehler.org diff --git a/htdocs/favicon.ico b/htdocs/favicon.ico new file mode 100644 index 0000000..402bd96 Binary files /dev/null and b/htdocs/favicon.ico differ diff --git a/htdocs/frontend/index.html b/htdocs/frontend/index.html index 4709868..732c30b 100644 --- a/htdocs/frontend/index.html +++ b/htdocs/frontend/index.html @@ -3,6 +3,9 @@ volkszaehler.org - web frontend + + + @@ -130,7 +133,10 @@

Hier können Sie einen existierenden Kanal über seine UUID hinzufügen

-

+ + + +
diff --git a/htdocs/frontend/javascripts/frontend.js b/htdocs/frontend/javascripts/frontend.js index 09bf440..b03d3f5 100644 --- a/htdocs/frontend/javascripts/frontend.js +++ b/htdocs/frontend/javascripts/frontend.js @@ -37,19 +37,31 @@ vz.wui.init = function() { // buttons $('button, input[type=button],[type=image]').button(); $('button[name=options-save]').click(function() { vz.options.save(); }); - $('#permalink').click(function() { // TODO add uuids - var u = window.location.protocol + '//' + + $('#permalink').click(function() { + var uuids = []; + var url = window.location.protocol + '//' + window.location.host + window.location.pathname + '?from=' + vz.options.plot.xaxis.min + '&to=' + vz.options.plot.xaxis.max; - window.location = u; + vz.entities.each(function(entity, parent) { + if (entity.active) { + uuids.push(entity.uuid); + } + }); + + uuids.unique().each(function(key, value) { + url += '&uuid=' + value; + }); + + window.location = url; }); $('button[name=entity-add]').click(function() { $('#entity-add').dialog('open'); }); $('#entity-subscribe input[type=button]').click(function() { try { vz.uuids.add($('#entity-subscribe input[type=text]').val()); + vz.uuids.save(); $('#entity-subscribe input[type=text]').val(''); $('#entity-add').dialog('close'); vz.entities.loadDetails(); @@ -80,16 +92,16 @@ vz.wui.init = function() { // auto refresh if (vz.options.refresh) { $('#refresh').attr('checked', true); - vz.wui.interval = window.setInterval(vz.wui.refresh, vz.options.refreshInterval); + vz.wui.timeout = window.setTimeout(vz.wui.refresh, 3000); } $('#refresh').change(function() { if ($(this).attr('checked')) { vz.options.refresh = true; - vz.wui.interval = window.setInterval(vz.wui.refresh, vz.options.refreshInterval); + vz.wui.timeout = window.setTimeout(vz.wui.refresh, 3000); } else { vz.options.refresh = false; - window.clearInterval(vz.wui.interval); + window.clearTimeout(vz.wui.timeout); } }); @@ -155,9 +167,12 @@ vz.wui.initEvents = function() { */ vz.wui.refresh = function() { var delta = vz.options.plot.xaxis.max - vz.options.plot.xaxis.min; + vz.options.plot.xaxis.max = new Date().getTime(); // move plot vz.options.plot.xaxis.min = vz.options.plot.xaxis.max - delta; // move plot vz.entities.loadData(); + + vz.wui.timeout = window.setTimeout(vz.wui.refresh, (delta / 100 < 3000) ? 3000 : delta / 100); // TODO update timeout after zooming }; /** @@ -272,9 +287,9 @@ vz.entities.show = function() { event.data.active = state; if (entity.type == 'group') { - entity.children.each(function(entity) { - $('#entity-' + entity.uuid + ' input[type=checkbox]').attr('checked', state); - entity.active = state; + entity.children.each(function(child) { + $('#entity-' + child.uuid + '.child-of-entity-' + entity.uuid + ' input[type=checkbox]').attr('checked', state); + child.active = state; }); } @@ -312,6 +327,7 @@ vz.entities.show = function() { .attr('alt', 'delete') .bind('click', entity, function(event) { vz.uuids.remove(event.data.uuid); + vz.uuids.save(); vz.entities.loadDetails(); }) ); @@ -435,24 +451,26 @@ vz.load = function(context, identifier, data, success) { /** * Parse URL GET parameters */ -vz.parseUrlVars = function() { - var vars = $.getUrlVars(); +vz.parseUrlParams = function() { + var vars = $.getUrlParams(); for (var key in vars) { if (vars.hasOwnProperty(key)) { switch (key) { case 'uuid': // add optional uuid from url - try { - vz.uuids.add(vars[key]); - } catch (exception) { - vz.wui.dialogs.exception(exception); - } + var uuids = (typeof vars[key] == 'string') ? [vars[key]] : vars[key]; // handle multiple uuids + uuids.each(function(index, uuid) { + try { vz.uuids.add(uuid); } catch (exception) { /* ignore exception */ } + }); break; + case 'from': vz.options.plot.xaxis.min = parseInt(vars[key]); break; + case 'to': vz.options.plot.xaxis.max = parseInt(vars[key]); break; + case 'debug': $.getScript('javascripts/firebug-lite.js'); break; @@ -512,7 +530,7 @@ vz.wui.dialogs.error = function(error, description, code) { modal: true, buttons: { Ok: function() { - $( this ).dialog( "close" ); + $(this).dialog('close'); } } }); diff --git a/htdocs/frontend/javascripts/helper.js b/htdocs/frontend/javascripts/helper.js index b4c40a0..20ff51a 100644 --- a/htdocs/frontend/javascripts/helper.js +++ b/htdocs/frontend/javascripts/helper.js @@ -55,8 +55,9 @@ var Exception = function(type, message, code) { * according to js language specification ECMA 1.6 */ Array.prototype.indexOf = function(n) { - for (var i = 0, l = this.length; i < l; i++) + for (var i = 0, l = this.length; i < l; i++) { if (n == this[i]) return i; + } }; Array.prototype.remove = function(n) { @@ -64,8 +65,9 @@ Array.prototype.remove = function(n) { }; Array.prototype.each = function(cb) { - for (var i = 0, l = this.length; i < l; i++) + for (var i = 0, l = this.length; i < l; i++) { cb(i, this[i]); + } }; Array.prototype.contains = function(n) { @@ -75,3 +77,13 @@ Array.prototype.contains = function(n) { Array.prototype.clear = function() { this.length = 0; } + +Array.prototype.unique = function () { + var r = new Array(); + this.each(function(key, value) { + if (!r.contains(value)) { + r.push(value); + } + }); + return r; +} diff --git a/htdocs/frontend/javascripts/init.js b/htdocs/frontend/javascripts/init.js index 26d3dbf..81e3ded 100644 --- a/htdocs/frontend/javascripts/init.js +++ b/htdocs/frontend/javascripts/init.js @@ -63,7 +63,7 @@ $(document).ready(function() { vz.definitions.load(); vz.uuids.load(); vz.options.load(); - vz.parseUrlVars(); + vz.parseUrlParams(); // initialize user interface vz.wui.init(); diff --git a/htdocs/frontend/javascripts/jquery/jquery-extensions.js b/htdocs/frontend/javascripts/jquery/jquery-extensions.js index e2978cc..c461e0c 100644 --- a/htdocs/frontend/javascripts/jquery/jquery-extensions.js +++ b/htdocs/frontend/javascripts/jquery/jquery-extensions.js @@ -29,18 +29,28 @@ * Get URL parameters */ $.extend( { - getUrlVars : function() { - var vars = [], hash; + getUrlParams : function() { + var vars = {}, hash; var hashes = window.location.href.slice( window.location.href.indexOf('?') + 1).split('&'); for (var i = 0; i < hashes.length; i++) { hash = hashes[i].split('='); - vars[hash[0]] = hash[1]; + switch (typeof vars[hash[0]]) { + case 'undefined': + vars[hash[0]] = hash[1]; + break; + + case 'string': + vars[hash[0]] = Array(vars[hash[0]]); + + case 'object': + vars[hash[0]].push(hash[1]); + } } return vars; }, - getUrlVar : function(name) { - return $.getUrlVars()[name]; + getUrlParam : function(name) { + return $.getUrlParams()[name]; } }); diff --git a/htdocs/frontend/javascripts/options.js b/htdocs/frontend/javascripts/options.js index b3d5a42..f61cf10 100644 --- a/htdocs/frontend/javascripts/options.js +++ b/htdocs/frontend/javascripts/options.js @@ -32,9 +32,8 @@ vz.options = { rounding: 1, render: 'lines', refresh: false, - refreshInterval: 5*1000, // 5 secs defaultInterval: 24*60*60*1000, // 1 day - timezoneOffset: -(new Date().getTimezoneOffset() * 60*1000) // TODO add option with timezone dropdown + timezoneOffset: -(new Date().getTimezoneOffset() * 60000) // TODO add option with timezone dropdown }; vz.options.plot = { diff --git a/htdocs/frontend/javascripts/uuid.js b/htdocs/frontend/javascripts/uuid.js index b343b22..2e5cf99 100644 --- a/htdocs/frontend/javascripts/uuid.js +++ b/htdocs/frontend/javascripts/uuid.js @@ -31,7 +31,6 @@ vz.uuids.add = function(uuid) { if (this.validate(uuid)) { if (!this.contains(uuid)) { this.push(uuid); - this.save(); } else { throw new Exception('UUIDException', 'UUID already added: ' + uuid); @@ -48,7 +47,6 @@ vz.uuids.add = function(uuid) { vz.uuids.remove = function(uuid) { if (this.contains(uuid)) { this.splice(this.indexOf(uuid), 1); // remove uuid from array - this.save(); } else { throw new Exception('UUIDException', 'UUID unkown: ' + uuid); @@ -66,7 +64,8 @@ vz.uuids.validate = function(uuid) { * Save uuids as cookie */ vz.uuids.save = function() { - $.setCookie('vz_uuids', this.join(';')); + var expires = new Date(new Date().getTime() + 31536e6); // expires in a year + $.setCookie('vz_uuids', this.join(';'), {expires: expires}); }; /** diff --git a/lib/Controller/AggregatorController.php b/lib/Controller/AggregatorController.php index 6aa543f..807f4b7 100644 --- a/lib/Controller/AggregatorController.php +++ b/lib/Controller/AggregatorController.php @@ -37,7 +37,7 @@ class AggregatorController extends EntityController { /** * Get aggregator */ - public function get($identifier) { + public function get($identifier = NULL) { $aggregator = parent::get($identifier); if ($aggregator instanceof Model\Aggregator) { diff --git a/lib/Controller/ChannelController.php b/lib/Controller/ChannelController.php index ade8a6c..6864cde 100644 --- a/lib/Controller/ChannelController.php +++ b/lib/Controller/ChannelController.php @@ -37,7 +37,7 @@ class ChannelController extends EntityController { /** * Get channel */ - public function get($identifier) { + public function get($identifier = NULL) { $channel = parent::get($identifier); if ($channel instanceof Model\Channel) { diff --git a/lib/Controller/DataController.php b/lib/Controller/DataController.php index 6c76d45..e786680 100644 --- a/lib/Controller/DataController.php +++ b/lib/Controller/DataController.php @@ -70,18 +70,9 @@ class DataController extends Controller { public function run($operation, array $identifiers = array()) { $ec = new EntityController($this->view, $this->em); + $entity = $ec->get($identifiers[0]); - if (count($identifiers) == 2) { // prototype: backend/data/uuid/port.json - $identifiers[0] = $ec->filter(array( - 'cuuid' => $identifiers[0], - 'port' => $identifiers[1] - )); - } - elseif (count($identifiers) == 1) { // assume UUID - $identifiers[0] = $ec->get($identifiers[0]); - } - - return parent::run($operation, $identifiers); + return $this->{$operation}($entity); } } diff --git a/lib/Controller/EntityController.php b/lib/Controller/EntityController.php index e03fde4..ded0dec 100644 --- a/lib/Controller/EntityController.php +++ b/lib/Controller/EntityController.php @@ -34,28 +34,34 @@ use Volkszaehler\Model; * @package default */ class EntityController extends Controller { + /** * Get entity * * @param string $identifier */ - public function get($uuid) { - if (!Util\UUID::validate($uuid)) { - throw new \Exception('Invalid UUID: ' . $uuid); + public function get($uuid = NULL) { + if (isset($uuid)) { + if (!Util\UUID::validate($uuid)) { + throw new \Exception('Invalid UUID: ' . $uuid); + } + + $dql = 'SELECT a, p + FROM Volkszaehler\Model\Entity a + LEFT JOIN a.properties p + WHERE a.uuid = :uuid'; + + $q = $this->em->createQuery($dql); + $q->setParameter('uuid', $uuid); + + try { + return $q->getSingleResult(); + } catch (\Doctrine\ORM\NoResultException $e) { + throw new \Exception('No entity found with UUID: ' . $uuid, 404); + } } - - $dql = 'SELECT a, p - FROM Volkszaehler\Model\Entity a - LEFT JOIN a.properties p - WHERE a.uuid = ?1'; - - $q = $this->em->createQuery($dql); - $q->setParameter(1, $uuid); - - try { - return $q->getSingleResult(); - } catch (\Doctrine\ORM\NoResultException $e) { - throw new \Exception('No entity found with UUID: ' . $uuid, 404); + else { // get public entities + return $this->filter(array('public' => TRUE)); } } @@ -82,49 +88,40 @@ class EntityController extends Controller { /** * Adds an entity to the uuids cookie + * + * @todo add to Model\Entity? * @param Model\Entity $entity */ protected function setCookie(Model\Entity $entity) { - if ($uuids = $this->view->request->getParameter('uuids', 'cookies')) { - $uuids = Util\JSON::decode($uuids); - } - else { - $uuids = new Util\JSON(); - } + $uuids = ($uuids = $this->view->request->getParameter('vz_uuids', 'cookies')) ? explode(';', $uuids) : array(); // add new UUID - $uuids->append($entity->getUuid()); - - // remove duplicates - $uuids->exchangeArray(array_unique($uuids->getArrayCopy())); + $uuids[] = $entity->getUuid(); // send new cookie to browser - setcookie('uuids', $uuids->encode(), 0, '/'); // TODO correct path + setcookie('vz_uuids', implode(';', array_unique($uuids)), 0, '/'); // TODO correct path } /** * Removes an entity from the uuids cookie + * * @param Model\Entity $entity + * @todo add to Model\Entity? */ protected function unsetCookie(Model\Entity $entity) { - if ($uuids = $this->view->request->getParameter('uuids', 'cookies')) { - $uuids = Util\JSON::decode($uuids); - } - else { - $uuids = new Util\JSON(); - } + $uuids = ($uuids = $this->view->request->getParameter('vz_uuids', 'cookies')) ? explode(';', $uuids) : array(); // remove old UUID - $uuids->exchangeArray(array_filter($uuids->getArrayCopy, function($uuid) use ($entity) { + $uuids = array_filter($uuids, function($uuid) use ($entity) { return $uuid != $entity->getUuid(); - })); + }); // send new cookie to browser - setcookie('uuids', $uuids->encode(), 0, '/'); // TODO correct path + setcookie('vz_uuids', implode(';', array_unique($uuids)), 0, '/'); // TODO correct path } /** - * Update/set/delete properties of properties + * Update/set/delete properties of entities */ protected function setProperties(Model\Entity $entity, $parameters) { foreach ($parameters as $parameter => $value) { @@ -141,14 +138,19 @@ class EntityController extends Controller { /** * Filter entites by properties + * + * @todo improve performance + * @param array of property => value filters + * @return array of entities */ public function filter(array $properties) { $dql = 'SELECT a, p FROM Volkszaehler\Model\Entity a LEFT JOIN a.properties p'; - $sqlWhere = array(); $i = 0; + $sqlWhere = array(); + $sqlParams = array(); foreach ($properties as $property => $value) { switch (Definition\PropertyDefinition::get($property)->getType()) { case 'string': @@ -158,9 +160,13 @@ class EntityController extends Controller { break; case 'boolean': - $value = ($value) ? 1 : 0; + $value = (int) $value; } - $sqlWhere[] = 'EXISTS (SELECT p' . $i . ' FROM \Volkszaehler\Model\Property p' . $i . ' WHERE p' . $i . '.name = \'' . $property . '\' AND p' . $i . '.value = ' . $value . ' AND p' . $i . '.entity = a)'; + $sqlWhere[] = 'EXISTS (SELECT p' . $i . ' FROM \Volkszaehler\Model\Property p' . $i . ' WHERE p' . $i . '.key = :key' . $i . ' AND p' . $i . '.value = :value' . $i . ' AND p' . $i . '.entity = a)'; + $sqlParams += array( + 'key' . $i => $property, + 'value' . $i => $value + ); $i++; } @@ -169,7 +175,7 @@ class EntityController extends Controller { } $q = $this->em->createQuery($dql); - return $q->getSingleResult(); + return $q->execute($sqlParams); } } diff --git a/lib/Interpreter/Interpreter.php b/lib/Interpreter/Interpreter.php index 092b001..55088f4 100644 --- a/lib/Interpreter/Interpreter.php +++ b/lib/Interpreter/Interpreter.php @@ -56,7 +56,7 @@ abstract class Interpreter implements InterpreterInterface { */ public function __construct(Model\Channel $channel, ORM\EntityManager $em, $from, $to) { $this->channel = $channel; - + // get dbal connection from EntityManager $this->conn = $em->getConnection(); diff --git a/lib/Model/Entity.php b/lib/Model/Entity.php index e7c6a02..b6ac43b 100644 --- a/lib/Model/Entity.php +++ b/lib/Model/Entity.php @@ -23,12 +23,10 @@ namespace Volkszaehler\Model; -use Volkszaehler\Definition; - use Doctrine\ORM; - use Doctrine\Common\Collections; use Volkszaehler\Util; +use Volkszaehler\Definition; /** * Entity superclass for all objects referenced by a UUID @@ -124,12 +122,17 @@ abstract class Entity { * @return array */ public function getProperties($prefix = NULL) { + /*$this->properties->filter(function($property) { + return substr($property->getKey(), 0, strlen($prefix)) == $prefix; + })->toArray();*/ + $properties = array(); foreach ($this->properties as $property) { if (substr($property->getKey(), 0, strlen($prefix)) == $prefix) { $properties[$property->getKey()] = $property->getValue(); } } + return $properties; } @@ -175,8 +178,9 @@ abstract class Entity { } /* - * Setter & Getter + * Setter & getter */ + public function getId() { return $this->id; } // read only public function getUuid() { return $this->uuid; } // read only public function getType() { return $this->type; } // read only diff --git a/lib/Model/Property.php b/lib/Model/Property.php index 9c0391b..24011ec 100644 --- a/lib/Model/Property.php +++ b/lib/Model/Property.php @@ -24,7 +24,6 @@ namespace Volkszaehler\Model; use Volkszaehler\Definition; - use Volkszaehler\Util; use Volkszaehler\Model; @@ -73,7 +72,6 @@ class Property { */ public function __construct(Model\Entity $entity, $key, $value) { $this->entity = $entity; - $this->key = $key; $this->value = $value; } @@ -118,6 +116,7 @@ class Property { /** * @PreRemove + * @todo blocks removal of entity */ public function checkRemove() { if (in_array($this->key, $this->entity->getDefinition()->getRequiredProperties())) { @@ -135,8 +134,9 @@ class Property { } /* - * Setter & Getter + * Setter & getter */ + public function getKey() { return $this->key; } public function getValue() { return $this->value; } public function getDefinition() { return Definition\PropertyDefinition::get($this->key); } diff --git a/lib/Router.php b/lib/Router.php index 520377d..1e14550 100644 --- a/lib/Router.php +++ b/lib/Router.php @@ -137,7 +137,7 @@ class Router { /** * Processes the request * - * Example: http://sub.domain.local/vz/backend/channel/550e8400-e29b-11d4-a716-446655440000/data.json?operation=edit&title=New Title + * Example: http://sub.domain.local/backend.php/channel/550e8400-e29b-11d4-a716-446655440000/data.json?operation=edit&title=New Title */ public function run() { $operation = self::getOperation($this->view->request); diff --git a/lib/Util/Debug.php b/lib/Util/Debug.php index 81f2273..9503c99 100644 --- a/lib/Util/Debug.php +++ b/lib/Util/Debug.php @@ -85,10 +85,10 @@ class Debug { self::$instance->messages[] = array( 'message' => $message, - //'file' => $info['file'], - //'line' => $info['line'], + 'file' => $info['file'], + 'line' => $info['line'], //'time' => date('r'), - 'args' => array_slice($info['args'], 1), + 'args' => array_slice($info['args'], 1) //'trace' => array_slice($trace, 1) ); } diff --git a/misc/controller/ethersex b/misc/controller/ethersex index 822c9dc..64a3546 160000 --- a/misc/controller/ethersex +++ b/misc/controller/ethersex @@ -1 +1 @@ -Subproject commit 822c9dce3a2c13de28af0080aa39e166831e19b1 +Subproject commit 64a3546f9ba8a0a4070f8b6ecc182643d5539699 diff --git a/misc/graphics/backend.dia b/misc/graphics/backend.dia new file mode 100644 index 0000000..092df86 Binary files /dev/null and b/misc/graphics/backend.dia differ diff --git a/misc/graphics/backend.png b/misc/graphics/backend.png new file mode 100644 index 0000000..b4e9157 Binary files /dev/null and b/misc/graphics/backend.png differ diff --git a/misc/graphics/favicon.ico b/misc/graphics/favicon.ico new file mode 100644 index 0000000..402bd96 Binary files /dev/null and b/misc/graphics/favicon.ico differ diff --git a/misc/graphics/favicon.png b/misc/graphics/favicon.png new file mode 100644 index 0000000..5a8c62e Binary files /dev/null and b/misc/graphics/favicon.png differ diff --git a/misc/graphics/favicon.svg b/misc/graphics/favicon.svg new file mode 100644 index 0000000..ba20a3b --- /dev/null +++ b/misc/graphics/favicon.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/misc/website b/misc/website new file mode 160000 index 0000000..d542e6a --- /dev/null +++ b/misc/website @@ -0,0 +1 @@ +Subproject commit d542e6a6817f90f01bffb50f481c308d9d0753da