--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<buildpath>
+ <buildpathentry kind="src" path=""/>
+ <buildpathentry kind="con" path="org.eclipse.php.core.LANGUAGE"/>
+</buildpath>
--- /dev/null
+/Deploy.sh
+/do.not.deploy.list
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>webcal</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.wst.common.project.facet.core.builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.wst.validation.validationbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.dltk.core.scriptbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.php.core.PHPNature</nature>
+ <nature>org.eclipse.wst.common.project.facet.core.nature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+../../skeleton/app/common
\ No newline at end of file
--- /dev/null
+../../skeleton/app/configurations
\ No newline at end of file
--- /dev/null
+<?php
+use \app\common as TC;
+
+$configArray = array(
+ 'local_js' => '',
+ 'local_css' => '',
+ 'local_pageType' => 'base',
+ 'local_title' => 'Lokales',
+);
--- /dev/null
+SNIPPET_NAV_START:
+ <li class="nav-item active">
+ <a class="nav-link" href="###url_home###">###start### <span class="sr-only">(current)</span></a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="###url_settings###">###settings###</a>
+ </li>
+ <li>
+
+SNIPPET_NAV_END:
+ <form class="form-inline my-2 my-lg-0" action="/###APP_NAME###/login/logout" method="post">
+ <button class="btn btn-outline-success my-2 my-sm-0 logout" name="buttonLogoutCommons" type="submit">###logout###</button>
+ </form>
+
+END:
--- /dev/null
+SNIPPET_ROOT:
+<!DOCTYPE html>
+<html lang="en" data-url="###url_current###">
+ <head>
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+###seo###
+ <link rel="icon" href="###PUBLIC_PREFIX###/public/icon/favicon.ico">
+
+ <title>###global_title### - ###local_title###</title>
+
+ <link href="###PUBLIC_PREFIX###/public/css/vendor/bootstrap.min-4.4.1.css" rel="stylesheet">
+ <link href="###PUBLIC_PREFIX###/public/css/page/common/jquery.datetimepicker.css" rel="stylesheet">
+ <link href="###PUBLIC_PREFIX###/public/css/page/common/global.css" rel="stylesheet">
+ <link href="###PUBLIC_PREFIX###/public/css/page/common/local.css" rel="stylesheet">
+
+ <!-- Custom styles for this template -->
+###local_css###
+ </head>
+ <body class="page-type-###local_pageType###">
+ <header>
+ <nav class="navbar navbar-expand-md fixed-top">
+ <!-- a class="navbar-brand" href="#">Navbar</a>
+ <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
+ <span class="navbar-toggler-icon"></span>
+ </button-->
+
+ <div class="collapse navbar-collapse" id="navbarTop">
+ <ul class="navbar-nav mr-auto">
+ <li class="nav-item active">
+ <a class="nav-link" href="###url_home###">###start### <span class="sr-only">(current)</span></a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="###url_settings###">###settings###</a>
+ </li>
+ <li>
+ <a class="nav-link" href="###url_impressum###" target="_blank" >Impressum</a>
+ </li>
+ <li>
+ <a class="nav-link" href="###url_privacy###" target="_blank" >Datenschutz</a>
+ </li>
+ </ul>
+ <form class="form-inline my-2 my-lg-0" action="/###APP_NAME###/login/logout" method="post">
+ <button class="btn btn-outline-success my-2 my-sm-0 logout" name="buttonLogoutCommons" type="submit">###logout###</button>
+ </form>
+ </div>
+ </nav>
+ </header>
+ <div class="container">
+ ###CONTENT###
+ </div><!-- /.container -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <!-- Version 4 -->
+ <!--
+ -->
+ <script src="###PUBLIC_PREFIX###/public/js/vendor/popper.min.js"></script>
+ <!-- script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
+ -->
+ <script src="###PUBLIC_PREFIX###/public/js/vendor/jquery-3.2.1.min.js"></script>
+ <!-- script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
+ -->
+ <script src="###PUBLIC_PREFIX###/public/js/vendor/bootstrap.min-4.4.1.js"></script>
+
+ <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
+ <script src="###PUBLIC_PREFIX###/public/js/vendor/ie10-viewport-bug-workaround.js"></script>
+ <!-- -->
+ <script src="###PUBLIC_PREFIX###/public/js/vendor/jquery.datetimepicker.full.min.js"></script>
+ <script src="###PUBLIC_PREFIX###/public/js/common/jquery.global.js"></script>
+ </body>
+</html>
+
+SNIPPET_LINK:
+ <link href="###PUBLIC_PREFIX###/public/css/###file###" rel="stylesheet">
+
+SNIPPET_COMMON_LEGEND:
+###txt_commonLegend### (###SNIPPET_COMMON_FILTER_ACTIVE###) ###count### ###txt_records###
+
+SNIPPET_COMMON_LEGEND_PAGED:
+###txt_commonLegend### (###SNIPPET_COMMON_FILTER_ACTIVE###) ###filtered### ###txt_records### (###txt_of### ###unfiltered###)
+
+SNIPPET_COMMON_PAGING_DATA:
+###txt_page### ###page### ###txt_of### ###pages###
+
+SNIPPET_COMMON_FILTER_INACTIVE:
+###txt_commonFilterInActive###
+
+SNIPPET_COMMON_FILTER_ACTIVE:
+<span class="filtered-text">###txt_commonFilterActive###</span>
+
+SNIPPET_PDF_FRAME:
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <!-- Bootstrap core CSS -->
+ <!-- link href="###PUBLIC_PREFIX###/public/css/vendor/bootstrap.min.css" rel="stylesheet">
+ -->
+ <link href="###PUBLIC_PREFIX###/public/css/page/###APP_NAME###/common/base.v1.css" rel="stylesheet">
+ <link href="###PUBLIC_PREFIX###/public/css/page/###APP_NAME###/common/pdf.v1.css" rel="stylesheet">
+
+ <!-- Custom styles for this template -->
+###local_css###
+ </head>
+ <body>
+ <div class="container">
+ ###CONTENT###
+ </div>
+ <div style="width: 100%; position: fixed; bottom: 7mm;">
+ <table border="0" width="100%" style="color: #0c0c0c; font-size: 50%;">
+ <tr>
+ <td colspan="3" style="text-align: center;">###pdf.company.large###</td>
+ </tr>
+ <tr>
+ <td>###pdf.company.normal###<br\></td>
+ <td>###pdf.phone###<br/>###pdf.fax###</td>
+ <td>###pdf.email###<br/>###pdf.site###</td>
+ </tr>
+ <tr>
+ <td>###pdf.address###</td>
+ <td>###pdf.mobile###</td>
+ <td>###pdf.ushd###</td>
+ </tr>
+ </table>
+ </footer>
+ </body>
+</html>
+
+SNIPPET_EXCEL_FRAME:
+<html xmlns:x=\"urn:schemas-microsoft-com:office:excel\">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <!--[if gte mso 9]>
+ <xml>
+ <x:ExcelWorkbook>
+ <x:ExcelWorksheets>
+ <x:ExcelWorksheet>
+ <x:Name>Stundenliste</x:Name>
+ <x:WorksheetOptions>
+ <x:Print>
+ <x:ValidPrinterInfo/>
+ </x:Print>
+ </x:WorksheetOptions>
+ </x:ExcelWorksheet>
+ </x:ExcelWorksheets>
+ </x:ExcelWorkbook>
+ </xml>
+ <![endif]-->
+ </head>
+ <body>
+ ###CONTENT###
+ </body>
+</html>
+
+SNIPPET_COMMON_IMG_EDIT:
+<img src="###PUBLIC_PREFIX###/public/icon/famfamfam/silk/pencil.png" alt="###iconEdit###" />
+
+SNIPPET_SORT_BUTTON:
+<img class="imageSortButton" data-name="###arg_1###" src="###PUBLIC_PREFIX###/public/icon/famfamfam/silk/arrow_down.png" alt="###arg_1###"/>
+
+SNIPPET_COMMON_IMG_EDIT_TABLE:
+<a href="###url_edit###"><img src="###PUBLIC_PREFIX###/public/icon/famfamfam/silk/pencil.png" alt="###iconEdit###" /></a>
+<img class="editInTable" src="###PUBLIC_PREFIX###/public/icon/famfamfam/silk/table_edit.png" alt="###iconChange###" />
+<img class = "hidden saveInTable" src="###PUBLIC_PREFIX###/public/icon/famfamfam/silk/database_save.png" alt="###iconSave###" />
+<img class = "hidden cancelInTable" src="###PUBLIC_PREFIX###/public/icon/famfamfam/silk/cancel.png" alt="###iconCancel###" />
+
+SNIPPET_COMMON_IN_TABLE_CONTROL:
+<button type="submit" class="hidden" id="buttonSaveInTable" name="buttonSaveInTable"></button>
+<button type="submit" hidden="hidden" id="buttonCancelInTable" name="buttonCancelInTable"></button>
+<input type="text" class="keyInTable" name="keyInTable" value="" />
+
+SNIPPET_COMMON_MESSAGE_IN_TABLE:
+<tr class="tr-invisible"><td></td><td class="messageInTable" colspan="99">###txt_helpEditInTable###</td></tr>
+
+SNIPPET_COMMON_PAGING:
+<div class="paging-block">
+ <input type="hidden" name="currentSortCriterion" id="currentSortCriterion" value="###val_currentSortCriterion###" />
+ <input type="hidden" name="currentSortDirection" id="currentSortDirection" value="###val_currentSortDirection###" />
+ <input type="hidden" name="lastSortCriterion" id="lastSortCriterion" value="###val_lastSortCriterion###" />
+ <input type="hidden" name="lastSortDirection" id="lastSortDirection" value="###val_lastSortDirection###" />
+ <button type="submit" hidden="hidden" id="buttonSort" name="buttonSort"></button>
+
+ <input type="hidden" name="pagingIndex" id="pagingIndex" value="###val_pagingIndex###" />
+ <div class="float-right paging-text">###txt_Filtered###: !filtered! ###txt_Unfiltered###: !unfiltered!
+ <select id="pagingSize" name="pagingSize">###opt_pagingSize###
+ </select> ###txt_linesPerPage###
+ </div>
+ <div class="float-left">
+ <ul class="pagination compact-block">
+###ITEMS###
+ </ul>
+ </div>
+ <div class="clear-both">
+ </div>
+</div>
+
+SNIPPET_COMMON_PAGING_ITEM:
+<li class="page-item"><a data-paging-index="###ix###" class="page-link" href="#">###no###</a></li>
+
+SNIPPET_COMMON_PAGING_ITEM_ACTIVE:
+<li class="page-item active"><a data-paging-index="###ix###" class="page-link" href="#">###no###</a></li>
+
+SNIPPET_COMMON_PAGING_GAP:
+<li class="page-item">...</li>
+
+SNIPPET_LEAVE_PAGE:
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <link href="###PUBLIC_PREFIX###/public/css/vendor/bootstrap.min.css" rel="stylesheet">
+ <link href="###PUBLIC_PREFIX###/public/css/page/common/global.css" rel="stylesheet">
+ <link href="###PUBLIC_PREFIX###/public/css/page/common/local.css" rel="stylesheet">
+ </head>
+<body>
+ <div class="container">
+ <div class="row">
+ <h3>Es gibt noch ungesicherte Daten.</h3>
+###ITEMS###
+ </div>
+ <div class="row">
+ <div class="col-md-2"> </div>
+ <div class="col-md-4"><button id="buttonLeavePage">Daten verwerfen</button></div>
+ <div class="col-md-2"> </div>
+ <div class="col-md-4"><button id="buttonLeavePage">Abbrechen</button></div>
+ </div>
+ </div>
+</body>
+</html>
+
+SNIPPET_LEAVE_PAGE_ITEM:
+ <div class="col-md-3">###label###</div>
+ <div class="col-md-9">###value###</div>
+
+SNIPPET_GENERAL_TABLE:
+<table>
+<thead>
+~heads~
+</thead>
+~rows~
+</table>
+
+SNIPPET_CALENDAR:
+<table class="table table-striped calendar">
+<thead>
+<tr>
+<th>###mon###</th><th>###tue###</th><th>###wed###</th>
+<th>###thu###</th><th>###fri###</th><th>###sat###</th><th>###sun###</th>
+</tr>
+</thead>
+<tbody>
+###ROWS###
+</tbody>
+</table>
+
+SNIPPET_CALENDAR_ROW:
+<tr>###COLS###
+</tr>
+
+SNIPPET_CALENDAR_COL:
+<td>###DATE######SEP######INFO###</td>
+
+END:
--- /dev/null
+<?php
+namespace app\locals;
+
+use lib\native as LN;
+use lib\native\Singleton;
+use app\common as TC;
+
+class Controller
+{
+
+ use LN\MVController, Model, View, Singleton, Service;
+
+ /**
+ * Instantiation is forbidden!
+ * Use Controller::getInstance() instead (Singleton).
+ */
+ private function __constructor()
+ {
+ // do nothing
+ }
+
+ /**
+ * Builds a map with (<key>, <value>) pairs from $references.
+ *
+ * @param string $references
+ * a blank separated list of keys
+ * @param array $config
+ * a map containing the values (with the same key)
+ * @return array|NULL
+ */
+ public function buildPlaceholderMap(string $references, array &$config): ?array
+ {
+ return null;
+ }
+
+ /**
+ * Sets the "breadcrumb" info line in a HTML text.
+ *
+ * @precondition: a macro ###breadcrumb### exists in the HTML text
+ *
+ * @param array $data
+ * a list of pairs [name, url], e.g. [['Projekte' FULL_URL_APP_PREFIX . '/projects/overview'], ['Neuanlage', '#']]
+ * @param string $html
+ * the HTML text with the expanded macro
+ */
+ public function setBreadcrumb(array $data, string $html, string $moduleType = 'std')
+ {
+ if (($ixMax = count($data) - 1) < 0) {
+ $html2 = '';
+ } else {
+ $html2 = "<ol class=\"breadcrumb\">\n";
+ $container = LN\Config::get('main_sys')['app'][APP_NAME];
+ $name1 = 'urlBreadcrumb' . LN\StringUtils::capitalize($moduleType);
+ $name2 = 'labelBreadcrumb' . LN\StringUtils::capitalize($moduleType);
+ if (! array_key_exists($name1, $container)) {
+ $url = FULL_URL_APP_PREFIX . '/overview/base';
+ $text = 'Start';
+ } else {
+ $url = LN\Config::get('main_sys')['app'][APP_NAME][$name1];
+ $text = LN\Config::get('main_sys')['app'][APP_NAME][$name2];
+ }
+ $html2 .= "<li class=\"breadcrumb-item\"><a href=\"$url\">$text</a></li>\n";
+ for ($ix = 0; $ix < $ixMax; $ix ++) {
+ $text = $data[$ix][0];
+ if ($text[0] === '#'){
+ $text = $this->translate($text);
+ } else {
+ $text = LN\StringUtils::textToHtml($text2);
+ }
+ $url = $data[$ix][1];
+ if (! empty($text)) {
+ $html2 .= "<li class=\"breadcrumb-item\"><a href=\"$url\">$text</a></li>\n";
+ }
+ }
+ $text = LN\StringUtils::textToHtml($data[$ixMax][0]);
+ $html2 .= "<li class=\"breadcrumb-item active\">$text</li>\n";
+ $html2 .= "</ol>\n";
+ }
+ $html = str_replace('###breadcrumb###', $html2, $html);
+ return $html;
+ }
+}
--- /dev/null
+<?php
+namespace app\locals;
+
+use lib\native as LN;
+use app\common as TC;
+
+trait Model
+{
+
+ use LN\ModelVC;
+
+ private $_sqlSnippetsModel = 1;
+
+ /**
+ * Initializes the module as a service.
+ */
+ public function initService()
+ {
+ $this->__initSQL();
+ }
+
+ /**
+ * Adds a member to a term (table userterm).
+ *
+ * @param int $member
+ * the member to add
+ * @param int $term
+ * the related term
+ */
+ public function addMember(int $member, int $term)
+ {
+ $sql = $this->_sqlSnippetsModel->getSnippet('SQL_INSERT_USERTERM');
+ $this->_db->prepareAndExecute($sql, [
+ ':member' => $member,
+ ':term' => $term,
+ ':user' => $_SESSION['shortname']
+ ]);
+ }
+
+ /**
+ * Deletes a member of a term (table userterm).
+ *
+ * @param int $member
+ * the member to add
+ * @param int $term
+ * the related term
+ */
+ public function subMember(int $member, int $term)
+ {
+ $sql = $this->_sqlSnippetsModel->getSnippet('SQL_DELETE_USERTERM');
+ $this->_db->prepareAndExecute($sql, [
+ ':member' => $member,
+ ':term' => $term
+ ]);
+ }
+
+ /**
+ * Assembles all members as a text.
+ *
+ * @param int $term
+ * the members related to this term will be assembled
+ * @return string the display names of the members, delimited by ' '
+ */
+ public function membersOfTerm(int $term): string
+ {
+ $sql = $this->_sqlSnippetsModel->getSnippet('SQL_USERTERM_BY_TERM');
+ $recs = $this->_db->readall($sql, [
+ ':term' => $term
+ ], false);
+ $rc = '';
+ foreach ($recs as &$rec) {
+ if (! empty($rc)) {
+ $rc .= ', ';
+ }
+ $rc .= $rec['user_displayname'];
+ }
+ return $rc;
+ }
+}
--- /dev/null
+<?php
+namespace app\locals;
+
+use lib\native as LN;
+use app\common as TC;
+
+/**
+ * Handles the "service" of the application.
+ * This is a command line tool.
+ *
+ * @author hm
+ *
+ */
+trait Service
+{
+
+ private $taskDirectory = ROOT . 'task';
+
+ private $progressDirectory = ROOT . 'progress';
+
+ private $isDaemon = false;
+
+ /**
+ * Prints an error message and an info about usage.
+ *
+ * @param string $message
+ * error message
+ */
+ private function argError(string $message)
+ {
+ echo "+++ $message\n";
+ echo "more info with 'service help'\n";
+ }
+
+ /**
+ * Starts a never ending loop: reads task from files and executes them.
+ *
+ * @param int $interval
+ * the waiting time in seconds between two task file searches
+ * @param string $taskDirectory
+ * null or the directory where the task files will be expected
+ * @param string $progressDirectory
+ * null or the directory where the progress files will be expected
+ */
+ public function daemon(array $argv, int $interval = 3, ?string $taskDirectory = null, ?string $progressDirectory = null)
+ {
+ $service = new LN\Service([$this, 'taskHandler'], $interval, $taskDirectory, $progressDirectory);
+ $service->daemon($argv);
+ }
+ /**
+ * Prints an error message.
+ *
+ * @param string $message
+ */
+ private function error(string $message)
+ {
+ echo "+++ $message\n";
+ }
+
+ /**
+ * Offers services outside of the web interface (as command line tool).
+ *
+ * @param array $argv
+ * the program arguments, e.g. ['create-charge', '19']
+ */
+ public function service(array $argv)
+ {
+ if (\count($argv) < 1) {
+ $this->usage("zu wenig Argumente");
+ } else {
+ $script = \array_shift($argv);
+ $cmd = \array_shift($argv);
+ switch ($cmd) {
+ case 'help':
+ $this->usage(null);
+ break;
+ case 'version':
+ require_once ROOT . 'config/changes/version.php';
+ echo 'Version: ' . APP_VERSION . "\nSkeleton-Version: " . SKELETON_VERSION . "\n";
+ break;
+ case 'test':
+ $this->test($argv);
+ break;
+ case 'daemon':
+ $this->daemon($argv);
+ break;
+ default:
+ echo "unknown subcommand: $cmd\nUse 'service help' for help\n";
+ break;
+ }
+ }
+ }
+
+ /**
+ * A test whether the service is working.
+ *
+ * Counts from
+ *
+ * @param array $argv
+ * expected: [<progress_id>, <count>]
+ */
+ private function test(array $argv)
+ {
+ if (count($argv) < 2) {
+ $this->argError('too few arguments)');
+ } elseif (($count = intval($argv[1])) == 0) {
+ LN\Service::progressWrite($argv[0], 0, 100, 100, 100);
+ $this->argError('invalid <count>: ' . $argv[0]);
+ } else {
+ $id = $argv[0];
+ for ($ix = 0; $ix < $count; $ix++){
+ LN\Service::progressWrite($id, 0, 100, $ix, $count);
+ \sleep(3);
+ }
+ LN\Service::progressWrite($id, 0, 100, 1, 1);
+ }
+ }
+/**
+ * Handles the tasks of this service.
+ *
+ * @param string $task
+ * the task to do, e.g. 'charge'
+ * @param array $args
+ * the task arguments, e.g. ['19']
+ * @return bool true: the task could be handled false: unknown task
+ */
+ public function taskHandler(string $task, array $args): bool
+ {
+ $locals = Controller::getInstance();
+ $rc = true;
+ echo "task found: $task\n";
+ switch ($task) {
+ case 'test':
+ $locals->test($args);
+ break;
+ default:
+ $rc = false;
+ break;
+ }
+ return $rc;
+ }
+
+
+ /**
+ * Displays the usage info.
+ *
+ * @param string $message
+ * null or the error message
+ */
+ private function usage(?string $message)
+ {
+ echo "usage: service <subcommand> <arguments>\n";
+ echo "<subcommands>:\n";
+ echo " charge <charge_id>\n";
+ echo " creates the pools, tokens and qrcodes of a charge\n";
+ echo " daemon\n";
+ echo " starts an daemon waiting for tasks given by task files in a directory\n";
+ echo " help\n";
+ echo " displays this info\n";
+ echo "..test <progress_id> <count>\n";
+ echo "....counts from 0 to <count> (for testing purpose)\n";
+ echo " version\n";
+ echo " displays the program version\n";
+ if ($message != null) {
+ echo "+++ $message\n";
+ }
+ }
+}
--- /dev/null
+<?php
+namespace app\locals;
+
+use lib\native as LN;
+use app\common as TC;
+use app\sysflags as TS;
+
+trait View
+{
+
+ use \lib\native\MViewC;
+
+ private $_build = [
+ 'common' => [
+ 'name' => 'common',
+ 'site' => 1,
+ 'config' => 1
+ ]
+ ];
+
+ /**
+ * Adapts the full page in a application depending way.
+ *
+ * @param string $content
+ * IN/OUT: the full page, may be changed
+ */
+ public function adaptFullPage(string &$content)
+ {
+ // do nothing
+ }
+
+}
--- /dev/null
+SQL_INSERT_USERTERM:
+insert into userterms
+ (userterm_user, userterm_term, created, createdby)
+values
+ (:member, :term, now(), :user)
+;
+
+SQL_DELETE_USERTERM:
+delete
+ from userterms
+where
+ userterm_term=:term and userterm_user=:member
+;
+
+SQL_USERTERM_BY_TERM:
+select
+ u.user_displayname
+from
+ userterms tt
+ left join loginusers u on u.user_id=tt.userterm_user
+where
+ userterm_term=:term
+order by user_displayname
+;
+
+END:
\ No newline at end of file
--- /dev/null
+../../skeleton/app/login
\ No newline at end of file
--- /dev/null
+../../skeleton/app/logs
\ No newline at end of file
--- /dev/null
+../../skeleton/app/overview
\ No newline at end of file
--- /dev/null
+../../skeleton/app/roles
\ No newline at end of file
--- /dev/null
+../../skeleton/app/sessions
\ No newline at end of file
--- /dev/null
+../../skeleton/app/settings
\ No newline at end of file
--- /dev/null
+../../skeleton/app/starters
\ No newline at end of file
--- /dev/null
+../../skeleton/app/sysflags
\ No newline at end of file
--- /dev/null
+<?php
+use \app\common as TC;
+
+$include = "common/terms.common.config.php";
+
+$configArray = array(
+ 'local_js' => '',
+ 'local_css' => '',
+ 'local_pageType' => 'base',
+ 'local_title' => 'Termine',
+ 'hasFilter' => TC\Controller::getInstance()->configurationAsBool('terms', 'filter:switch'),
+
+ // PHP_HOOK_CONFIG_BASE
+ '!EoA!' => ''
+);
--- /dev/null
+SNIPPET_ROOT:
+<div class="form">###breadcrumb###</div>
+
+<form id="main-form" action="###url_action###" method="post">
+<button type="submit" id="buttonRedirectJS" hidden="hidden" name="buttonRedirectJS">Clicked by JS</button>
+<button type="submit" id="buttonUpdateJS" hidden="hidden" name="buttonUpdateJS">Clicked by JS</button>
+<div class="form">
+<!-- panel.fields -->
+<div class="row">
+ <div class="col-md-12"><span class="pseudo-label"></span></div>
+
+###SNIPPET_IF_2###
+</div>
+<!-- end.panel.fields -->
+
+###SNIPPET_COMMON_PAGING###
+<table class="table table-striped db-table">
+<thead>
+ <tr>
+ <th></th>
+ <th>Id###COMMON_SNIPPET[SNIPPET_SORT_BUTTON,sort_term_id]###</th>
+ <th>Datum###COMMON_SNIPPET[SNIPPET_SORT_BUTTON,sort_term_date]###</th>
+ <th>Bezeichnung###COMMON_SNIPPET[SNIPPET_SORT_BUTTON,sort_term_name]###</th>
+ <th>Platzanweiser</th>
+ <th>Bemerkung</th>
+ <th></th>
+ </tr>
+</thead>
+<tbody>
+###ROWS###
+</tbody>
+</table>
+</div>
+</form>
+
+
+SNIPPET_IF_2:
+ <div class="col-md-12"><button type="submit" name="buttonNew">Neuer Termin</button></div> <div class="col-md-12"><span class="pseudo-label"></span></div>
+
+
+SNIPPET_ROW:
+<tr>
+ <td><a href="/###APP_NAME###/terms/edit?termID=###term_id###"><img src="###PUBLIC_PREFIX###/public/icon/famfamfam/silk/pencil.png" alt="###alt_edit###" /></a>
+ </td>
+ <td>###term_id###</td>
+ <td>###term_date###</td>
+ <td>###term_name###</td>
+ <td>###members###</td>
+ <td>###term_info###</td>
+ <td><a href="/###APP_NAME###/terms/delete?action=delete&termID=###term_id###"><img src="###PUBLIC_PREFIX###/public/icon/famfamfam/silk/delete.png" alt="###alt_delete###" /></a>
+ </td>
+</tr>
+
+
+SNIPPET_DUMMY_FOR_HOOK:
+<!-- PHP_HOOK_HTML_SNIPPETS not used-->
+
+END:
--- /dev/null
+<?php
+use \app\common as TC;
+
+
+
+$configArray = array(
+ 'local_js' => '',
+ 'local_css' => '',
+ 'data' => 'Daten', 'parent_title' => 'Termine',
+
+ // PHP_HOOK_CONFIG_COMMON
+ '!EoA!' => ''
+);
--- /dev/null
+SNIPPET_FIELDS:
+<!-- page.common -->
+<!-- end.page.common -->
+
+SNIPPET_DUMMY_FOR_HOOK:
+<!-- PHP_HOOK_HTML_SNIPPETS not used-->
+
+END:
--- /dev/null
+<?php
+use \app\common as TC;
+
+$include = "common/terms.common.config.php";
+
+$configArray = array(
+ 'local_js' => '',
+ 'local_css' => '',
+ 'local_pageType' => 'delete',
+ 'local_title' => 'Termin löschen',
+
+ // PHP_HOOK_CONFIG_DELETE
+ '!EoA!' => ''
+);
--- /dev/null
+SNIPPET_ROOT:
+<div class="form">
+ ###breadcrumb###
+</div>
+<form id="main-form" action="###url_action###" method="post">
+<button type="submit" id="buttonRedirectJS" hidden="hidden" name="buttonRedirectJS">Clicked by JS</button>
+<button type="submit" id="buttonAbortJS" hidden="hidden" name="buttonAbortJS">Clicked by JS</button>
+<button type="submit" id="buttonUpdateJS" hidden="hidden" name="buttonUpdateJS">Clicked by JS</button>
+<input type="hidden" name="term_id" value="###val_term_id###" />
+<!-- panel.fieldgroup -->
+<fieldset>
+<div class="row">
+ <input type="hidden" id="term_id" name="term_id" value="###val_term_id###" />
+<div class="col-md-2"><label>Bezeichnung</label></div> <div class="col-md-10"><input class="edittext" type="text" id="term_name" name="term_name" value="###val_term_name###" maxlength="32" /><!-- err_term_name --></div>
+<div class="col-md-2"><label>Datum</label></div> <div class="col-md-10"><input class="editdatetime datetimepicker" type="text" id="term_date" name="term_date" value="###val_term_date###" /><!-- err_term_date --></div>
+<div class="col-md-2"><label>Bemerkungen</label></div> <div class="col-md-10"><textarea class="edittext" rows="3" cols="60" id="term_info" name="term_info" maxlength="4000">###val_term_info###</textarea><!-- err_term_info --></div>
+ <div class="col-md-2"><span class="pseudo-label"></span></div>
+ <div class="col-md-4"><button type="submit" name="buttonDelete">Löschen</button></div> <div class="col-md-2"><span class="pseudo-label"></span></div>
+ <div class="col-md-4"><button type="submit" name="buttonClose">Schließen</button></div></div>
+</fieldset>
+<!-- end.panel.fieldgroup -->
+</form>
+
+SNIPPET_DUMMY_FOR_HOOK:
+<!-- PHP_HOOK_HTML_SNIPPETS not used-->
+
+END:
--- /dev/null
+<?php
+use \app\common as TC;
+
+$include = "common/terms.common.config.php";
+
+$configArray = array(
+ 'local_js' => '',
+ 'local_css' => '',
+ 'local_pageType' => 'edit',
+ 'local_title' => 'Termin ändern',
+
+ // PHP_HOOK_CONFIG_EDIT
+ '!EoA!' => ''
+);
--- /dev/null
+SNIPPET_ROOT:
+<div class="form">
+ ###breadcrumb###
+</div>
+<form id="main-form" action="###url_action###" method="post">
+<button type="submit" id="buttonRedirectJS" hidden="hidden" name="buttonRedirectJS">Clicked by JS</button>
+<button type="submit" id="buttonAbortJS" hidden="hidden" name="buttonAbortJS">Clicked by JS</button>
+<button type="submit" id="buttonUpdateJS" hidden="hidden" name="buttonUpdateJS">Clicked by JS</button>
+<input type="hidden" name="term_id" value="###val_term_id###" />
+<!-- panel.fieldgroup -->
+<fieldset>
+<div class="row">
+<div class="col-md-2"><label>Bezeichnung</label></div> <div class="col-md-4"><input class="edittext" type="text" id="term_name" name="term_name" value="###val_term_name###" maxlength="32" /><!-- err_term_name --></div>
+<div class="col-md-2"><label>Datum</label></div> <div class="col-md-4"><input class="editdatetime datetimepicker" type="text" id="term_date" name="term_date" value="###val_term_date###" /><!-- err_term_date --></div>
+<div class="col-md-2"><label>Bemerkungen</label></div> <div class="col-md-10"><textarea class="edittext" rows="3" cols="60" id="term_info" name="term_info" maxlength="4000">###val_term_info###</textarea><!-- err_term_info --></div>
+ <input type="hidden" id="membersShdw" name="membersShdw" value="###val_membersShdw###" />
+<div class="col-md-2"><label>Platzanweiser</label></div> <div class="col-md-10"><textarea class="edittext readonly readonly" rows="3" cols="60" id="members" name="members">###val_members###</textarea><!-- err_members --></div>
+ <div class="col-md-2"><label for="member">Ändern</label></div>
+ <div class="col-md-4"><select class="editselect edittext" id="member" name="member">###opt_member###</select><!-- err_member --></div>
+ <div class="col-md-3"><button type="submit" name="buttonAdd">Hinzufügen</button></div> <div class="col-md-3"><button type="submit" name="buttonSub">Entfernen</button></div> <div class="col-md-12"><span class="pseudo-label"></span></div>
+
+###SNIPPET_IF_2###
+###SNIPPET_IF_3###
+ <div class="col-md-3"><button type="submit" name="buttonClose">Schließen</button></div></div>
+</fieldset>
+<!-- end.panel.fieldgroup -->
+</form>
+
+
+SNIPPET_IF_2:
+###errors###
+ <div class="col-md-2"><span class="pseudo-label"></span></div>
+ <div class="col-md-4"><button type="submit" name="buttonSave">Speichern</button></div>
+
+SNIPPET_IF_3:
+ <div class="col-md-6"><span class="pseudo-label"></span></div>
+
+SNIPPET_DUMMY_FOR_HOOK:
+<!-- PHP_HOOK_HTML_SNIPPETS not used-->
+
+END:
--- /dev/null
+<?php
+use \app\common as TC;
+
+$include = "common/terms.common.config.php";
+
+$configArray = array(
+ 'local_js' => '',
+ 'local_css' => '',
+ 'local_pageType' => 'new',
+ 'local_title' => 'Neuer Termin',
+
+ // PHP_HOOK_CONFIG_NEW
+ '!EoA!' => ''
+);
--- /dev/null
+SNIPPET_ROOT:
+<div class="form">
+ ###breadcrumb###
+</div>
+<form id="main-form" action="###url_action###" method="post">
+<button type="submit" id="buttonRedirectJS" hidden="hidden" name="buttonRedirectJS">Clicked by JS</button>
+<button type="submit" id="buttonAbortJS" hidden="hidden" name="buttonAbortJS">Clicked by JS</button>
+<button type="submit" id="buttonUpdateJS" hidden="hidden" name="buttonUpdateJS">Clicked by JS</button>
+<!-- panel.fieldgroup -->
+<fieldset>
+<div class="row">
+ <input type="hidden" id="term_id" name="term_id" value="###val_term_id###" />
+<div class="col-md-2"><label>Bezeichnung</label></div> <div class="col-md-10"><input class="edittext" type="text" id="term_name" name="term_name" value="###val_term_name###" maxlength="32" /><!-- err_term_name --></div>
+<div class="col-md-2"><label>Datum</label></div> <div class="col-md-10"><input class="editdatetime datetimepicker" type="text" id="term_date" name="term_date" value="###val_term_date###" /><!-- err_term_date --></div>
+<div class="col-md-2"><label>Bemerkungen</label></div> <div class="col-md-10"><textarea class="edittext" rows="3" cols="60" id="term_info" name="term_info" maxlength="4000">###val_term_info###</textarea><!-- err_term_info --></div>
+###errors###
+ <div class="col-md-2"><span class="pseudo-label"></span></div>
+ <div class="col-md-4"><button type="submit" name="buttonSave">Speichern</button></div> <div class="col-md-2"><span class="pseudo-label"></span></div>
+ <div class="col-md-4"><button type="submit" name="buttonClose">Schließen</button></div></div>
+</fieldset>
+<!-- end.panel.fieldgroup -->
+</form>
+
+SNIPPET_DUMMY_FOR_HOOK:
+<!-- PHP_HOOK_HTML_SNIPPETS not used-->
+
+END:
--- /dev/null
+SQL_SUFFIX_GETALL:
+ORDER BY
+ !order!
+!paging!
+;
+
+SQL_SUFFIX_GETALL_COUNT:
+;
+
+SQL_TERM_BY_NAME:
+SELECT
+ *
+FROM
+ terms
+WHERE
+ term_name=:name
+;
+
+SQL_BY_ID_TERM:
+SELECT
+ *
+FROM
+ terms
+WHERE
+ term_id = :termID
+;
+
+SQL_DELETE_BY_ID_TERM:
+DELETE FROM
+ terms
+WHERE
+ term_id = :termID
+;
+
+SQL_TERMS_DEFAULT_ORDER:
+term_date
+
+SQL_TERMS_BASE_GETALL:
+SELECT
+ tt.*,'dummy' as members
+FROM
+ terms tt
+WHERE
+ tt.deletedat is null
+
+SQL_TERMS_BASE_GETALL_COUNT:
+SELECT
+ count(*)
+FROM
+ terms tt
+WHERE
+ tt.deletedat is null
+
+SQL_TERMS_NEW_INSERT:
+INSERT INTO terms (
+ created,
+ createdby,
+ term_name,
+ term_date,
+ term_info
+) VALUES (
+ now(),
+ :loggeduser,
+ :name,
+ :date,
+ :info
+);
+
+
+
+SQL_TERMS_EDIT_UPDATE:
+UPDATE terms set
+ changed=now(),
+ changedby=:loggeduser,
+ term_name = :name,
+ term_date = :date,
+ term_info = :info
+WHERE
+ term_id = :termID
+;
+
+
+
+
+END:
--- /dev/null
+drop table if exists terms;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE terms(
+ term_id int(10) unsigned NOT NULL AUTO_INCREMENT,
+ term_name varchar(32),
+ term_date timestamp,
+ term_info text,
+ created timestamp NULL,
+ createdby varchar(16),
+ changed timestamp NULL,
+ changedby varchar(16),
+ deletedat timestamp NULL,
+ deletedby varchar(16),
+ PRIMARY KEY (term_id)
+);
+
+-- ! if no records in select * from configurations where configuration_scope='terms' and configuration_property='required:base'
+INSERT INTO configurations
+(configuration_scope, configuration_property, configuration_order, configuration_type, configuration_value, configuration_description, configuration_changed_by)
+VALUES
+('terms', 'required:base', 0, 'string', 'term_id,term_name', 'Pflichtfelder bei terms. Trennung mit Komma', 'wk');
+-- ! endif
+insert into starters (starter_name, starter_label, starter_parent, starter_image, starter_link)
+values (
+'terms', 'Terms', 'start', 'start/sysflag.png', '/webcal/configurations/base'
+);
+insert into rolestarter (starter_id, role_id, order_no)
+values((select max(starter_id) from starters where starter_name='terms'), 1, 1000),
+ ((select max(starter_id) from starters where starter_name='terms'), 2, 1000),
+ ((select max(starter_id) from starters where starter_name='terms'), 3, 1000)
+;
+
+
+drop table if exists userterms;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE userterms(
+ term_id int(10) unsigned NOT NULL AUTO_INCREMENT,
+ userterm_term int(10) unsigned,
+ userterm_user int(10) unsigned,
+ created timestamp NULL,
+ createdby varchar(16),
+ changed timestamp NULL,
+ changedby varchar(16),
+ deletedat timestamp NULL,
+ deletedby varchar(16),
+ PRIMARY KEY (term_id)
+);
+
+-- ! if no records in select * from configurations where configuration_scope='userterms' and configuration_property='required:base'
+INSERT INTO configurations
+(configuration_scope, configuration_property, configuration_order, configuration_type, configuration_value, configuration_description, configuration_changed_by)
+VALUES
+('terms', 'required:base', 0, 'string', 'term_id,userterm_term,userterm_user,created,createdby,changed,changedby,deletedat,deletedby', 'Pflichtfelder bei terms. Trennung mit Komma', 'wk');
+-- ! endif
+insert into starters (starter_name, starter_label, starter_parent, starter_image, starter_link)
+values (
+'terms', 'Terms', 'start', 'start/sysflag.png', '/webcal/configurations/base'
+);
+insert into rolestarter (starter_id, role_id, order_no)
+values((select max(starter_id) from starters where starter_name='terms'), 1, 1000),
+ ((select max(starter_id) from starters where starter_name='terms'), 2, 1000),
+ ((select max(starter_id) from starters where starter_name='terms'), 3, 1000)
+;
+
--- /dev/null
+<?php
+namespace app\terms;
+
+use \lib\native as LN;
+ // PHP_HOOK_CONTROLLER_USE_DEFS
+
+final class Controller
+{
+
+ use LN\MVController, Model,View;
+ // PHP_HOOK_CONTROLLER_USE_TRAITS
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->__initSQL();
+ // PHP_HOOK_CONTROLLER_CONSTRUCTOR
+ }
+ // PHP_HOOK_CONTROLLER_CUSTOMIZED_METHODS
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+namespace app\terms;
+
+use lib\native as LN;
+use app\common as TC;
+use app\locals as LOC;
+
+trait Model {
+
+ use LN\ModelVC;
+
+ private $_sqlSnippetsModel = 1;
+
+ private $dateFormat = 'd.m.Y';
+ private $dateTimeFormat = 'd.m.Y-H:i';
+
+
+ /**
+ * Selects a record by the name.
+ *
+ * @param string $name
+ * field value to inspect
+ * @return array the db record
+ */
+ public function termByName(string $name)
+ {
+ $sql = $this->_sqlSnippetsModel->getSnippet('SQL_TERM_BY_NAME');
+ $rc = $this->_db->readSingleRecord($sql, [
+ ':name' => $term_name
+ ], true);
+ LN\DB::changeDateColumn($rc, 'term_date', $this->dateTimeFormat, true);
+ LN\DB::changeDateColumn($rc, 'created', $this->dateTimeFormat, true);
+ LN\DB::changeDateColumn($rc, 'changed', $this->dateTimeFormat, true);
+ LN\DB::changeDateColumn($rc, 'deletedat', $this->dateTimeFormat, true);
+ return $rc;
+ }
+
+ /**
+ * Returns the db record given by id.
+ *
+ * @param int $termID
+ * primary key
+ * @return array the database record
+ */
+ public function termByID(int $termID)
+ {
+ $sql = $this->_sqlSnippetsModel->getSnippet('SQL_BY_ID_TERM');
+ $rc = $this->_db->readSingleRecord($sql, [
+ ':termID' => $termID
+ ]);
+ LN\DB::changeDateColumn($rc, 'term_date', $this->dateTimeFormat, true);
+ LN\DB::changeDateColumn($rc, 'created', $this->dateTimeFormat, true);
+ LN\DB::changeDateColumn($rc, 'changed', $this->dateTimeFormat, true);
+ LN\DB::changeDateColumn($rc, 'deletedat', $this->dateTimeFormat, true);
+ return $rc;
+ }
+ /**
+ * Deletes a record given by its primary key.
+ *
+ * @param int $id
+ * primary key
+ * return bool true: success
+ */
+ public function deleteTermById(int $id, array &$record)
+ {
+ $common = TC\Controller::getInstance();
+ $common->putToLogDelete('terms', 'term_id', $record);
+ $sql = $this->_sqlSnippetsModel->getSnippet('SQL_DELETE_BY_ID_TERM');
+ $rc = $this->_db->prepareAndExecute($sql, [
+ ':termID' => $id
+ ]);
+ return $rc !== FALSE;
+ }
+ /**
+ * Returns all records filtered by scope and property.
+ *
+ * @param string $sqlReplacement
+ * null or a string with (search <sep> replacement) pairs preceeded by <sep>
+ * Example: ':!idList!:1,2,4:!order!:name'
+ * @param LN\PagingData $pagingData
+ * Info about sorting and paging
+ * @return array the records matching the filter expressions
+ */
+ public function getAllTermsBase(?string $sqlReplacement, LN\PagingData &$pagingData, array &$record)
+ {
+ // PHP_HOOK_MODEL_TERMS_BASE_BODY_GETALL + PHP_HOOK_SQL_TERMS_BASE_ALL + PHP_HOOK_SQL_TERMS_BASE_ALL_COUNT
+ $sql = $this->_sqlSnippetsModel->getSnippet('SQL_TERMS_BASE_GETALL');
+ $sql .= "\n" . $this->_sqlSnippetsModel->getSnippet('SQL_SUFFIX_GETALL');
+ $search = null;
+ // PHP_HOOK_MODEL_TERMS_BASE_GETALL_FIX_ARGS
+ if ($sqlReplacement != null){
+ $parts = explode($sqlReplacement[0], substr($sqlReplacement, 1));
+ $search = [];
+ $replacement = [];
+ for ($ix = 0; $ix < count($parts) / 2; $ix++){
+ array_push($search, $parts[2*$ix]);
+ array_push($replacement, $parts[2*$ix+1]);
+ }
+ }
+
+ $sqlCount = $this->_sqlSnippetsModel->getSnippet('SQL_TERMS_BASE_GETALL_COUNT');
+
+ $sqlCount .= "\n" . $this->_sqlSnippetsModel->getSnippet('SQL_SUFFIX_GETALL_COUNT');
+ if ($search != null){
+ $sqlCount = str_replace($search, $replacement, $sqlCount);
+ }
+ // PHP_HOOK_MODEL_TERMS_BASE_GETALL_START
+ $params = [
+ ];
+ // PHP_HOOK_MODEL_TERMS_BASE_GETALL_PARAM_FIX
+ $countFiltered = intval($this->_db->readUniqueValue($sqlCount, $params));
+ $pagingData->setFilteredRecords($countFiltered);
+ $order = $this->_sqlSnippetsModel->getSnippet('SQL_TERMS_DEFAULT_ORDER');
+ // PHP_HOOK_MODEL_TERMS_BASE_PATCH_ORDER
+ $order = $pagingData->getOrder($order);
+ $sql = str_replace('!order!', $order, $sql);
+
+ $size = $pagingData->pagingSize;
+ if ($size <= 0){
+ $sql = str_replace('!paging!', '', $sql);
+ } else {
+ $offset = max(0, $pagingData->pagingIndex * $size);
+ $sql = str_replace('!paging!', "limit $offset, $size", $sql);
+ }
+ // PHP_HOOK_MODEL_TERMS_BASE_GETALL
+ if ($search != null){
+ $sql = str_replace($search, $replacement, $sql);
+ }
+ $rc = $this->_db->readAll($sql, $params, FALSE);
+ $pagingData->unfilteredRecords = $this->_db->countOfAll('terms');
+ if (count($rc) > 0 && \array_key_exists('term_date', $rc[0])) {
+ LN\DB::changeDateColumn($rc, 'term_date', $this->dateTimeFormat);
+ }
+ if (count($rc) > 0 && \array_key_exists('created', $rc[0])) {
+ LN\DB::changeDateColumn($rc, 'created', $this->dateTimeFormat);
+ }
+ if (count($rc) > 0 && \array_key_exists('changed', $rc[0])) {
+ LN\DB::changeDateColumn($rc, 'changed', $this->dateTimeFormat);
+ }
+ if (count($rc) > 0 && \array_key_exists('deletedat', $rc[0])) {
+ LN\DB::changeDateColumn($rc, 'deletedat', $this->dateTimeFormat);
+ }
+
+ // PHP_HOOK_MODEL_TERMS_BASE_GETALL_ADAPTION
+ return $rc;
+ }
+ /**
+ * Creates a new db record.
+ *
+ * @param array $record
+ * the record data as map: column_name -> value
+ * @return int primary key
+ */
+ public function insertByRecordNewTerm(array &$record)
+ {
+ $common = TC\Controller::getInstance();
+ $id = 0;
+ $sql = $this->_sqlSnippetsModel->getSnippet('SQL_TERMS_NEW_INSERT');
+ $params = [
+ ':loggeduser' => $_SESSION['shortname'],
+ ':name' => $record['term_name'],
+ ':date' => $record['term_date'] != null ? LN\DB::dateTimeToDbDateTime($record['term_date']) : null,
+ ':info' => $record['term_info'],
+ ];
+ // PHP_HOOK_MODEL_NEW_TERMS_INSERT_NEW
+ if ($this->_db->prepareAndExecute($sql, $params)) {
+ $id = $this->_db->lastInsertId();
+ if ($record != null){
+ $common->putToLogInsert('terms', 'term_id', $id);
+ }
+ }
+ return $id;
+ }
+ /**
+ * Updates a database record.
+ *
+ * @param int $termID
+ * primary key
+ * @param array $record
+ * the record data as map: column_name -> value
+ */
+ public function updateByRecordEditTerm(int $termID, array &$record)
+ {
+ $common = TC\Controller::getInstance();
+ $sql = $this->_sqlSnippetsModel->getSnippet('SQL_TERMS_EDIT_UPDATE');
+ $params = [
+ ':loggeduser' => $_SESSION['shortname'],
+ ':termID' => $record['termID'],
+ ':name' => $record['term_name'],
+ ':date' => $record['term_date'] != null ? LN\DB::dateTimeToDbDateTime($record['term_date']) : null,
+ ':info' => $record['term_info'],
+ ];
+ // PHP_HOOK_MODEL_EDIT_TERMS_UPDATE
+ if ($record != null){
+ $common->putToLogUpdate('terms', 'term_id', $termID, $record, null, ['termID']);
+ }
+ return $this->_db->prepareAndExecute($sql, $params);
+ }
+
+
+
+ // PHP_HOOK_MODEL_METHODS
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+namespace app\terms;
+use lib\native as LN;
+use app\common as TC;
+use app\locals as LOC;
+use app\sysflags as TS;
+use app\sessions as TSE;
+use lib\native\IFieldValidator;
+use lib\native\InputField;
+
+trait View {
+
+ use \lib\native\MViewC;
+ // PHP_HOOK_VIEW_USE_TRAIT
+ private $_build = [
+ 'base' => [
+ 'name' => 'base',
+ 'site' => 1,
+ 'config' => 1
+ ],
+ 'new' => [
+ 'name' => 'new',
+ 'site' => 1,
+ 'config' => 1
+ ],
+ 'edit' => [
+ 'name' => 'edit',
+ 'site' => 1,
+ 'config' => 1
+ ],
+ 'delete' => [
+ 'name' => 'delete',
+ 'site' => 1,
+ 'config' => 1
+ ]
+ ];
+ public $_moduleType = 'backend';
+ /**
+ * Returns the configuration of the common page.
+ *
+ * @return array the configuration
+ */
+ public function getCommonConfig()
+ {
+ // common config is included in edit config:
+ $config = $this->_getConfiguration($this->_build['edit'], 'edit');
+ return $config;
+ }
+ /**
+ * Handles AJAX requests.
+ */
+ public function ajax()
+ {
+ [
+ $snippets,
+ $config
+ ] = $this->_getSnippetsAndConfig($this->_build['edit'], 'edit');
+ $common = TC\Controller::getInstance();
+ $fieldSet = $this->buildCommonFieldSet('edit', URL_DOMAIN_APP_PREFIX . '/terms/ajax');
+ $answer = '';
+ if (! \array_key_exists('ajaxMode', $_POST)) {
+ error_log('Terms::ajax(): Missing argument "ajaxMode"');
+ } else {
+ switch ($_POST['ajaxMode']) {
+ case 'newChanged':
+ $answer = $common->testNewChanged($fieldSet);
+ break;
+ case 'editChanged':
+ $record = $this->termById($_POST['termID']);
+ $answer = $common->testNewChanged($fieldSet);
+ break;
+ default:
+ error_log('Terms::ajax(): unknown mode: ' . $_POST['ajaxMode']);
+ break;
+ }
+ }
+ $this->answerAjax($answer ?? '');
+ }
+ // PHP_HOOK_VIEW_METHODS
+
+ /**
+ * Returns the HTML text of the base page (without frame).
+ *
+ * @return string the HTML text of the base page (overview table)
+ */
+ public function buildBase(): string
+ {
+ $common = TC\Controller::getInstance();
+ $locals = LOC\Controller::getInstance();
+ // PHP_HOOK_BASE_INIT
+ [
+ $snippets,
+ $config
+ ] = $this->_getSnippetsAndConfig($this->_build['base'], 'base');
+ $content = $snippets->getSnippet('SNIPPET_ROOT', $config);
+ $breadcrumbs = [
+ [
+ $config['local_title'],
+ '#'
+ ]
+ ];
+ // PHP_HOOK_BASE_BREADCRUMB
+ $content = $this->_setBreadcrumb($breadcrumbs, $content, $this->_moduleType);
+ $validator = new TermsValidator($this);
+ $urlAction = FULL_URL_APP_PREFIX . '/terms/base';
+ $fieldSet = new LN\FieldSet('base', $config, $urlAction, $validator);
+ $common->addPagingAndSortingFields($fieldSet);
+
+ // PHP_HOOK_BASE_FIELDS
+ $fieldSet->setFromPost();
+ // PHP_HOOK_BASE_START
+ $storage = TSE\Controller::getInstance()->getStorage($_SESSION['hash'], 'terms', 'base');
+ $fieldSet->setFromArray($storage->getData());
+ // use all fields:
+ $storageFields = null;
+ // PHP_HOOK_BASE_SET_STORAGE_FIELDS
+ $storage->fetchFromFieldset($fieldSet, $storageFields);
+ if (count($_POST) == 0){
+ // First call
+ // PHP_HOOK_BASE_FIRST_CALL
+ }
+ // PHP_HOOK_BASE_FIELDS_READY
+ $pagingData = $common->getPagingAndSortingData($fieldSet, $storage);
+ $empty = false;
+ if (\array_key_exists('buttonNew', $_POST)) {
+ $linkPage = '/terms/new';
+ // PHP_HOOK_BASE_REDIRECT_BUTTONNEW
+ if ($linkPage != null) {
+ LN\Bootstrap::redirectUrl(URL_DOMAIN_APP_PREFIX . $linkPage);
+ $content = null;
+ }
+ // PHP_HOOK_BASE_BUTTONS
+ } else {
+ $templateRow = $snippets->getSnippet('SNIPPET_ROW', []);
+ // PHP_HOOK_BASE_TEMPLATE_ROW
+ $rows = '';
+ $record = [];
+ $sqlReplacement = '';
+ // PHP_HOOK_BASE_BEFORE_GET_ALL
+ $records = $this->getAllTermsBase($sqlReplacement, $pagingData, $record);
+ $content = $common->setLegendWithPages($pagingData, $content, ! $empty);
+ $editAllowed = true;
+ $deleteAllowed = true;
+ // === start PHP_HOOK_BASE_TABLE_RECORDS_ADAPTION
+ $deleteAllowed = $_SESSION['roleID'] <= 2;
+ // === end PHP_HOOK_BASE_TABLE_RECORDS_ADAPTION
+ $rowNo = 0;
+ foreach ($records as &$rec) {
+ $rowNo++;
+ // === start PHP_HOOK_BASE_TABLE_ROW_ADAPTION
+ $rec['members'] = $locals->membersOfTerm($rec['term_id']);
+ // === end PHP_HOOK_BASE_TABLE_ROW_ADAPTION
+ $html = $common->rowFromDatabase($templateRow, $rec);
+ if (! $editAllowed){
+ $html = $common->deleteEditIcon($html);
+ }
+ if (! $deleteAllowed){
+ $html = $common->deleteDeleteIcon($html);
+ }
+ $rows .= $html;
+ }
+ $content = str_replace('###ROWS###', $rows, $content);
+ // PHP_HOOK_BASE_HTML
+ $common->switchConditional($content, 'SNIPPET_IF_2', null, 'role', '1,2', false, $snippets, $config);
+ $dummyStr = null;
+ $map = $locals->buildPlaceholderMap('', $config);
+ // PHP_HOOK_BASE_HTML_REPLACEMENT
+ $content = $common->replaceInHtmlWithErrors($content, 'terms', $fieldSet, $map, $dummyStr, $pagingData, $storage);
+ }
+ // PHP_HOOK_BASE_EXIT
+ return $content;
+ }
+
+ /**
+ * Builds the html text for creation in page new (without frame).
+ *
+ * @return string the HTML text of the "new" page
+ */
+ public function buildNew(): string
+ {
+ $sysflags = TS\Controller::getInstance();
+ $common = TC\Controller::getInstance();
+ $locals = LOC\Controller::getInstance();
+ // PHP_HOOK_NEW_INIT
+ [
+ $snippets,
+ $config
+ ] = $this->_getSnippetsAndConfig($this->_build['new'], 'new');
+ // PHP_HOOK_NEW_FIELDSET_START
+ $content = null;
+ $validator = new TermsValidator($this);
+ $urlAction = FULL_URL_APP_PREFIX . '/terms/new';
+ $fieldSet = new LN\FieldSet('new', $config, $urlAction, $validator);
+ $fieldTerm_id = new LN\InputField($fieldSet, "term_id", ':db:hidden:', 'int', null, 'Id');
+ $fieldName = new LN\InputField($fieldSet, "term_name", ':db:', 'text', null, 'Bezeichnung');
+ $fieldDate = new LN\InputField($fieldSet, "term_date", ':db:', 'datetime', null, 'Datum');
+ $fieldInfo = new LN\InputField($fieldSet, "term_info", ':db:', 'text', null, 'Bemerkungen');
+ // PHP_HOOK_NEW_FIELDSET_COMPLETE_FIELDS
+ $fieldSet->setFromPost();
+ // PHP_HOOK_NEW_START
+ $errorMessages = '';
+ if (\array_key_exists('buttonClose', $_POST)) {
+ $linkPage = '/terms/base';
+ // PHP_HOOK_NEW_REDIRECT_BUTTONCLOSE
+ if ($linkPage != null) {
+ LN\Bootstrap::redirectUrl(URL_DOMAIN_APP_PREFIX . $linkPage);
+ $content = null;
+ }
+ } elseif (\array_key_exists('buttonSave', $_POST)) {
+ if (! $common->validate('terms.new.store', $fieldSet)) {
+ $errorMessages = $fieldSet->errorsAsHtml();
+ } else {
+ $record = [];
+ $record['term_name'] = $fieldSet->getField('term_name')->value;
+ $record['term_date'] = LN\StringUtils::stringToDateTime($fieldSet->getField('term_date')->value, $error);
+ $record['term_info'] = $fieldSet->getField('term_info')->value;
+ // PHP_HOOK_NEW_BEFORE_INSERT
+ $term_id = $this->insertByRecordNewTerm($record);
+ $_POST['term_id'] = $term_id;
+ // PHP_HOOK_NEW_AFTER_INSERT
+ $linkPage = '/terms/edit?termID=<term_id>';
+ $linkPage = str_replace('<term_id>', \array_key_exists('term_id', $_GET) ? $_GET['term_id'] : $_POST['term_id'], $linkPage);
+ // PHP_HOOK_NEW_REDIRECT_BUTTONSAVE
+ if ($linkPage != null) {
+ LN\Bootstrap::redirectUrl(URL_DOMAIN_APP_PREFIX . $linkPage);
+ $content = null;
+ }
+ // PHP_HOOK_NEW_STORE_END
+ }
+ } else {
+ if (! isset($_POST['term_name'])) {
+ // first entry:
+ // PHP_HOOK_NEW_FIRST_CALL
+ } else {
+ // PHP_HOOK_NEW_NOT_FIRST_CALL
+ if (! $common->validate('terms.new', $fieldSet)) {
+ $errorMessages = $fieldSet->errorsAsHtml();
+ } else {
+ // PHP_HOOK_NEW_VALIDATED
+ }
+ }
+ }
+ if ($content === null) {
+ $breadcrumbs = [
+ [
+ $config['parent_title'],
+ 'base'
+ ],
+ [
+ $config['local_title'],
+ '#'
+ ]
+ ];
+ $content = $snippets->getSnippet('SNIPPET_ROOT', $config);
+ // PHP_HOOK_NEW_BREADCRUMBS
+ $content = $this->_setBreadcrumb($breadcrumbs, $content, $this->_moduleType);
+ $include = $snippets->getSnippet('SNIPPET_FIELDS', $config, '');
+ $content = str_replace('###SNIPPET_FIELDS###', $include, $content);
+ // PHP_HOOK_NEW_HTML
+ $map = $locals->buildPlaceholderMap('', $config);
+ // PHP_HOOK_NEW_HTML_REPLACEMENT
+ $content = $common->replaceInHtmlWithErrors($content, 'terms', $fieldSet, $map, $errorMessages);
+ }
+ // PHP_HOOK_NEW_EXIT
+ return $content;
+ }
+
+ /**
+ * Builds the html text for editing in page edit (without frame).
+ *
+ * @return string the HTML text of the "edit" page
+ */
+ public function buildEdit(): string
+ {
+ $sysflags = TS\Controller::getInstance();
+ $common = TC\Controller::getInstance();
+ $locals = LOC\Controller::getInstance();
+ // PHP_HOOK_EDIT_INIT
+ $termID = isset($_GET['termID']) ? $_GET['termID'] : (isset($_POST['term_id']) ? $_POST['term_id'] : -1);
+ [
+ $snippets,
+ $config
+ ] = $this->_getSnippetsAndConfig($this->_build['edit'], 'edit');
+ // PHP_HOOK_EDIT_FIELDSET_START
+ $content = null;
+ $validator = new TermsValidator($this);
+ $urlAction = FULL_URL_APP_PREFIX . '/terms/edit';
+ $fieldSet = new LN\FieldSet('edit', $config, $urlAction, $validator);
+ $fieldTerm_id = new LN\InputField($fieldSet, "term_id", ':db:hidden:', 'int');
+ $fieldName = new LN\InputField($fieldSet, "term_name", ':db:', 'text', null, 'Bezeichnung');
+ $fieldDate = new LN\InputField($fieldSet, "term_date", ':db:', 'datetime', null, 'Datum');
+ $fieldInfo = new LN\InputField($fieldSet, "term_info", ':db:', 'text', null, 'Bemerkungen');
+ $fieldMembers = new LN\InputField($fieldSet, "members", ':readonly:', 'text', null, 'Platzanweiser');
+ $fieldMembersShdw = new LN\InputField($fieldSet, "membersShdw", ':isshadow:hidden:', 'text');
+ $fieldMember = new LN\InputField($fieldSet, "member", ':undef:', 'combobox', null, 'Ändern');
+ // === start PHP_HOOK_EDIT_FIELDSET_COMPLETE_FIELDS
+ $common->fillUserCombobox($fieldMember, [1, 27, 30], false);
+ // === end PHP_HOOK_EDIT_FIELDSET_COMPLETE_FIELDS
+ $fieldSet->setFromPost();
+ // PHP_HOOK_EDIT_START
+ if (isset($_POST['term_name'])) {
+ $termID = \array_key_exists('term_id', $_POST) ? $_POST['term_id'] : -1;
+ } else {
+ if (! \array_key_exists('termID', $_GET) || $_GET['termID'] <= 0) {
+ LN\Bootstrap::redirectUrl(URL_DOMAIN_APP_PREFIX . '/terms/base');
+ }
+ if (! isset($record)){
+ $record = $this->termById($termID);
+ }
+ $fieldSet->setFromRecord($record);
+ // === start PHP_HOOK_EDIT_FIRST_CALL_EARLY
+ $fieldMember->value = $_SESSION['userID'];
+ // === end PHP_HOOK_EDIT_FIRST_CALL_EARLY
+ }
+ // PHP_HOOK_EDIT_START2
+ $errorMessages = '';
+ if (\array_key_exists('buttonClose', $_POST)) {
+ $linkPage = '/terms/base';
+ // PHP_HOOK_EDIT_REDIRECT_BUTTONCLOSE
+ if ($linkPage != null) {
+ LN\Bootstrap::redirectUrl(URL_DOMAIN_APP_PREFIX . $linkPage);
+ $content = null;
+ }
+ } elseif (\array_key_exists('buttonSave', $_POST)) {
+ if (! $common->validate('terms.edit.store', $fieldSet)) {
+ $errorMessages = $fieldSet->errorsAsHtml();
+ } else {
+ $record = [];
+ $id = LN\StringUtils::intOrNull($fieldSet->getField('term_id')->value);
+ $record['termID'] = $id;
+ $record['term_name'] = $fieldSet->getField('term_name')->value;
+ $record['term_date'] = LN\StringUtils::stringToDateTime($fieldSet->getField('term_date')->value, $error);
+ $record['term_info'] = $fieldSet->getField('term_info')->value;
+ // PHP_HOOK_EDIT_BEFORE_UPDATE
+ $this->updateByRecordEditTerm($id, $record);
+ // PHP_HOOK_EDIT_REDIRECT_AFTER_UPDATE
+ $linkPage = '/terms/base';
+ // PHP_HOOK_EDIT_REDIRECT_BUTTONSAVE
+ if ($linkPage != null) {
+ LN\Bootstrap::redirectUrl(URL_DOMAIN_APP_PREFIX . $linkPage);
+ $content = null;
+ }
+ // PHP_HOOK_EDIT_STORE_END
+ }
+ } else {
+ if (! isset($_POST['term_name'])) {
+ // first entry:
+ // PHP_HOOK_EDIT_FIRST_CALL
+ } else {
+ // PHP_HOOK_EDIT_NOT_FIRST_CALL
+ if (! $common->validate('terms.edit', $fieldSet)) {
+ $errorMessages = $fieldSet->errorsAsHtml();
+ } else {
+ // === start PHP_HOOK_EDIT_VALIDATED
+ if (\array_key_exists('buttonAdd', $_POST)){
+ $locals->addMember($fieldMember->value, $termID);
+ } elseif (\array_key_exists('buttonSub', $_POST)){
+ $locals->subMember($fieldMember->value, $termID);
+ }
+ // === end PHP_HOOK_EDIT_VALIDATED
+ }
+ }
+ }
+ if ($content === null) {
+ $breadcrumbs = [
+ [
+ $config['parent_title'],
+ 'base'
+ ],
+ [
+ $config['local_title'],
+ '#'
+ ]
+ ];
+ $content = $snippets->getSnippet('SNIPPET_ROOT', $config);
+ // PHP_HOOK_EDIT_BREADCRUMBS
+ $content = $this->_setBreadcrumb($breadcrumbs, $content, $this->_moduleType);
+ $include = $snippets->getSnippet('SNIPPET_FIELDS', $config, '');
+ $content = str_replace('###SNIPPET_FIELDS###', $include, $content);
+ $storageAllowed = true;
+ $buttonName = 'buttonSave';
+ // === start PHP_HOOK_EDIT_HTML
+ $fieldMembers->value = $locals->membersOfTerm($termID);
+ // === end PHP_HOOK_EDIT_HTML
+ $common->switchConditional($content, 'SNIPPET_IF_2', null, 'role', '1,2', false, $snippets, $config);
+ $common->switchConditional($content, 'SNIPPET_IF_3', null, 'role', '3,4', false, $snippets, $config);
+ if (! $storageAllowed) {
+ $content = $common->deleteButton($content, $buttonName);
+ }
+ $map = $locals->buildPlaceholderMap('', $config);
+ // PHP_HOOK_EDIT_HTML_REPLACEMENT
+ $content = $common->replaceInHtmlWithErrors($content, 'terms', $fieldSet, $map, $errorMessages);
+ }
+ // PHP_HOOK_EDIT_EXIT
+ return $content;
+ }
+
+ /**
+ * Handles the deletion request of a record.
+ *
+ * Deletion can also be done with changing a state property.
+ *
+ * @return string the HTML text of the deletion dialog
+ */
+ private function buildDelete(): ?string
+ {
+ // PHP_HOOK_DELETE_INIT
+ [
+ $snippets,
+ $config
+ ] = $this->_getSnippetsAndConfig($this->_build['delete'], 'delete');
+ $sysflags = TS\Controller::getInstance();
+ $record = null;
+ if (\array_key_exists('termID', $_GET)){
+ $termID = $_GET['termID'];
+ $record = $this->termById($termID);
+
+ } else {
+ $termID = \array_key_exists('term_id', $_POST) ? $_POST['term_id'] : -1;
+
+ }
+ if ($record == null){
+ $record = $this->termById($termID);
+ }
+ $common = TC\Controller::getInstance();
+ $locals = LOC\Controller::getInstance();
+ $content = $snippets->getSnippet('SNIPPET_ROOT', $config);
+ $breadcrumbs = [
+ [
+ $config['parent_title'],
+ 'base'
+ ],
+ [
+ $config['local_title'],
+ '#'
+ ]
+ ];
+ // PHP_HOOK_DELETE_BREADCRUMBS
+ $content = $this->_setBreadcrumb($breadcrumbs, $content, $this->_moduleType);
+ // PHP_HOOK_DELETE_START
+ $validator = null;
+ $urlAction = FULL_URL_APP_PREFIX . '/terms/delete';
+ $fieldSet = new LN\FieldSet('delete', $config, $urlAction, $validator);
+ $fieldId = new LN\InputField($fieldSet, "term_id", ':hidden:');
+ $fieldTerm_id = new LN\InputField($fieldSet, "term_id", ':db:hidden:', 'int', null, 'Id');
+ $fieldName = new LN\InputField($fieldSet, "term_name", ':db:', 'text', null, 'Bezeichnung');
+ $fieldDate = new LN\InputField($fieldSet, "term_date", ':db:', 'datetime', null, 'Datum');
+ $fieldInfo = new LN\InputField($fieldSet, "term_info", ':db:', 'text', null, 'Bemerkungen');
+ // PHP_HOOK_DELETE_FIELDSET_COMPLETE_FIELDS
+ $fieldSet->setAttributeForAll('readonly');
+ $fieldSet->setFromPost();
+ // PHP_HOOK_DELETE_START
+ if ($record != null){
+ $fieldSet->setFromRecord($record);
+ }
+ $fieldId->value = $termID;
+ if (\array_key_exists('buttonClose', $_POST)) {
+ $linkPage = '/terms/base';
+ // PHP_HOOK_DELETE_REDIRECT_BUTTONCLOSE
+ if ($linkPage != null) {
+ LN\Bootstrap::redirectUrl(URL_DOMAIN_APP_PREFIX . $linkPage);
+ $content = null;
+ }
+ } elseif (isset($_POST['buttonDelete'])) {
+ $this->deleteTermById(intval($termID), $record);
+ $linkPage = '/terms/base';
+ // PHP_HOOK_DELETE_REDIRECT_BUTTONDELETE
+ if ($linkPage != null) {
+ LN\Bootstrap::redirectUrl(URL_DOMAIN_APP_PREFIX . $linkPage);
+ $content = null;
+ }
+ }
+ $deleteAllowed = true;
+ $buttonName = 'buttonDelete';
+ // PHP_HOOK_DELETE_HTML
+ if (! $deleteAllowed) {
+ $content = $common->deleteButton($content, $buttonName);
+ }
+ $map = $locals->buildPlaceholderMap('', $config);
+ // PHP_HOOK_DELETE_HTML_REPLACEMENT
+ $content = $common->replaceInHtmlWithErrors($content, 'terms', $fieldSet, $map);
+ // PHP_HOOK_DELETE_EXIT
+ return $content;
+ }
+
+ /**
+ * Builds and displays the "base" page.
+ * Method will be called by URL.
+ *
+ * @return string the html text of the full page
+ * only used for unit testing
+ */
+ public function base()
+ {
+ // PHP_HOOK_VIEW_BUILD_METHOD_BASE
+ $html = $this->buildBase();
+
+ $macros = $this->_getConfiguration($this->_build['base'], 'base');
+ $common = TC\Controller::getInstance();
+ $locals = LOC\Controller::getInstance();
+ $html = $common->buildFullPage($macros, $html);
+ $locals->adaptFullPage($html);
+ $this->_showHTML($html);
+ return $html;
+ }
+
+ /**
+ * Builds and displays the "new" page.
+ * Method will be called by URL.
+ *
+ * @return string the html text of the full page
+ * only used for unit testing
+ */
+ public function new()
+ {
+ // PHP_HOOK_VIEW_BUILD_METHOD_NEW
+ $html = $this->buildNew();
+
+ $macros = $this->_getConfiguration($this->_build['new'], 'new');
+ $common = TC\Controller::getInstance();
+ $locals = LOC\Controller::getInstance();
+ $html = $common->buildFullPage($macros, $html);
+ $locals->adaptFullPage($html);
+ $this->_showHTML($html);
+ return $html;
+ }
+
+ /**
+ * Builds and displays the "edit" page.
+ * Method will be called by URL.
+ *
+ * @return string the html text of the full page
+ * only used for unit testing
+ */
+ public function edit()
+ {
+ // PHP_HOOK_VIEW_BUILD_METHOD_EDIT
+ $html = $this->buildEdit();
+
+ $macros = $this->_getConfiguration($this->_build['edit'], 'edit');
+ $common = TC\Controller::getInstance();
+ $locals = LOC\Controller::getInstance();
+ $html = $common->buildFullPage($macros, $html);
+ $locals->adaptFullPage($html);
+ $this->_showHTML($html);
+ return $html;
+ }
+
+ /**
+ * Builds and displays the "delete" page.
+ * Method will be called by URL.
+ *
+ * @return string the html text of the full page
+ * only used for unit testing
+ */
+ public function delete()
+ {
+ // PHP_HOOK_VIEW_BUILD_METHOD_DELETE
+ $html = $this->buildDelete();
+
+ $macros = $this->_getConfiguration($this->_build['delete'], 'delete');
+ $common = TC\Controller::getInstance();
+ $locals = LOC\Controller::getInstance();
+ $html = $common->buildFullPage($macros, $html);
+ $locals->adaptFullPage($html);
+ $this->_showHTML($html);
+ return $html;
+ }
+
+}
+class TermsValidator implements LN\IFieldValidator
+{
+ private $_controller;
+ private $_parent;
+ /**
+ * Constructor.
+ *
+ * @param Controller $controller
+ * the controller of the abbreviations module
+ */
+ public function __construct(Controller $controller)
+ {
+ $this->_controller = $controller;
+ $parent = new TC\Validator();
+ $this->setValidatorParent($parent);
+ }
+ /**
+ *
+ * {@inheritdoc}
+ * @see \lib\native\IFieldValidator::getValidatorParent()
+ */
+ public function getValidatorParent(): ?IFieldValidator
+ {
+ return $this->_parent;
+ }
+
+ /**
+ *
+ * {@inheritdoc}
+ * @see \lib\native\IFieldValidator::setValidatorParent()
+ */
+ public function setValidatorParent(IFieldValidator $parent): void
+ {
+ $this->_parent = $parent;
+ }
+ /**
+ *
+ * {@inheritdoc}
+ * @see \lib\native\IFieldValidator::validateInputField()
+ */
+ public function validateInputField(InputField $field): bool
+ {
+ $rc = true;
+ $common = TC\Controller::getInstance();
+ $locals = LOC\Controller::getInstance();
+ // PHP_HOOK_VALIDATION_START
+ // PHP_HOOK_VALIDATE_INPUT_FIELD
+ return $rc;
+ }
+}
+?>
+
--- /dev/null
+../../skeleton/app/users
\ No newline at end of file
--- /dev/null
+../../../skeleton/config/changes/skeletonversion.php
\ No newline at end of file
--- /dev/null
+<?php
+/*
+ * Versionsnummern:
+ * DB_VERSION ist die Datenbankschemaversion, die das Programm erwartet.
+ * In der Datenbank steht die aktuelle Version des DB-Schemas. Ist beim Programmeintritt die aktuelle kleiner als die erwartete,
+ * werden Scripte ausgeführt, um das Schema zu aktualisieren.
+ *
+ * Konvention für Entwickler:
+ * Ist eine Änderung der DB notwendig, z.B. eine neue Tabelle oder ein neuer Datensatz, so muss die erwartete Versionsnummer
+ * hochgesetzt werden und ein SQL-Script in diesem Verzeichnis (config/changes) erstellt werden.
+ * Beispiel für das Script der Version 2017.11.01.00:
+ * db.v2017.11.01.00.sql
+ * Bei Änderungen der DB für die Unittests:
+ * testdb.v2017.11.01.00.sql
+ *
+ * Die anderen Versionsnummern dienen nur der Information.
+ */
+define ( 'APPLICATION_VERSION', 'WCL2020.05.24.00' );
+define ( 'DB_VERSION', '2018.11.28.00' );
+define ( 'TEST_DB_VERSION', '2018.11.07.00' );
+define ( 'LIB_VERSION', defined ( 'SKELETON_VERSION' ) ? SKELETON_VERSION : '???' );
+?>
--- /dev/null
+../../../skeleton/config/sys/error.php
\ No newline at end of file
--- /dev/null
+../../../skeleton/config/sys/loader.php
\ No newline at end of file
--- /dev/null
+/webcal.sql.gz
--- /dev/null
+../skeleton/lib
\ No newline at end of file
--- /dev/null
+../../../../../skeleton/public/css/page/common/global.css
\ No newline at end of file
--- /dev/null
+../../../../../skeleton/public/css/page/common/jquery.datetimepicker.css
\ No newline at end of file
--- /dev/null
+@charset "UTF-8";
+
+a {
+ color: #859319;
+}
+
+fieldset {
+ background-color: #e4eda3;
+ border-radius: 4px;
+}
+
+legend {
+ font-size: 0.9em;
+}
+
+input {
+ border-radius: 3px;
+ margin-bottom: 0.5em;
+}
+
+select {
+ border-radius: 3px;
+ margin-bottom: 0.5em;
+ height: 1.8em;
+}
+
+button {
+ border-radius: 3px;
+}
+
+label, .pseudo-label {
+ text-align: left;
+}
+
+.page-item.active .page-link {
+ background-color: #B1C903
+}
+/* Hintergrundfarbe der Navigation-Menue-Leiste */
+nav.navbar {
+ background-color: #B1C903;
+}
+/* Schrift in Menüleiste */
+.navbar a.nav-link {
+ color: #EEE;
+ /* background-color: #e4eda3; */
+}
+
+.tabs-modules {
+ display: flex;
+ flex-wrap: wrap;
+ padding-left: 0;
+ margin-bottom: 0;
+ list-style: none;
+}
+
+.tab-module-link {
+ display: block;
+ text-decoration: none;
+}
+
+.tab-pane.active {
+ background-color: #e4eda3;
+}
+
+.tabs-modules a.tab-module-link {
+ background-color: #f0f0f0;
+ border-color: #111;
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
+ border: solid 1px black;
+ border-bottom: none;
+ padding: 0.5em;
+}
+
+.tabs-modules a.tab-module-link, .tabs-modules a:link.tab-module-link,
+ .tabs-modules a:visited.tab-module-link, .tabs-modules a:active.tab-module-link,
+ .tabs-modules a:hover.tab-module-link {
+ text-decoration: none;
+ color: #859319;
+}
+
+.tabs-modules a.tab-module-link.active {
+ background-color: #e4eda3;
+}
+
+.tabs-modules a:hover.tab-module-link {
+ font-weight: bold;
+ color: #7c841e;
+}
+
+.tabs-modules a:hover.tab-module-link.active {
+ text-decoration: none;
+ font-weight: normal;
+}
+
+.tabs-modules a:hover.nav-link {
+ color: #444;
+ font-weight: bold;
+}
+
+.page-item.active .page-link {
+ border-color: #879901;
+}
+
+.page-link {
+ color: #B1C903;
+}
+
+.page-link:hover {
+ color: #0;
+}
+
+.paging-text {
+ font-size: 0.8em;
+}
+
+.editdatetime, .edittextmiddle, .editselectshort, .edittextshort,
+ .editnumber {
+ width: 100%;
+}
+
+.readonly {
+ background-color: #e4eda3;
+ border: 1px solid #B1C903;
+}
+
+input, .pseudo-field {
+ width: 100%;
+}
+
+.filtered-text {
+ background-color: #e4eda3;
+}
\ No newline at end of file
--- /dev/null
+../../../../../skeleton/public/css/page/common/pdf.global.css
\ No newline at end of file
--- /dev/null
+../../../../../skeleton/public/css/page/overview/overview.global.css
\ No newline at end of file
--- /dev/null
+@charset "UTF-8";
+
+a.overview-cell, a:visited.overview-cell, a:link.overview-cell, a:active.overview-cell,
+ .dashboard-link a, .dashboard-link a:visited, .dashboard-link a:link,
+ .dashboard-link a:active {
+ border: 1px solid #B1C903;
+ color: #859319;
+ box-shadow: 0 0 5px 0 #88961e;
+}
+
+a:hover.overview-cell {
+ background-color: rgba(177, 201, 3, 0.5);
+}
\ No newline at end of file
--- /dev/null
+../../../skeleton/public/css/vendor
\ No newline at end of file
--- /dev/null
+../../skeleton/public/fonts
\ No newline at end of file
--- /dev/null
+../../../skeleton/public/icon/famfamfam
\ No newline at end of file
--- /dev/null
+kirchturm.ico
\ No newline at end of file
--- /dev/null
+../../../skeleton/public/icon/msg
\ No newline at end of file
--- /dev/null
+../../../skeleton/public/icon/start
\ No newline at end of file
--- /dev/null
+../../../skeleton/public/img/vendor
\ No newline at end of file
--- /dev/null
+<?php
+use lib\native as LN;
+
+date_default_timezone_set('Europe/Berlin');
+ini_set('default_charset', 'UTF-8');
+mb_internal_encoding('UTF-8');
+setLocale(LC_CTYPE, 'DE_de.UTF-8');
+
+define('TIME_START', microtime(TRUE));
+define('MEM_START', memory_get_usage(TRUE));
+
+define('DS', DIRECTORY_SEPARATOR);
+define('ROOT', $_SERVER['DOCUMENT_ROOT'] . DS);
+
+define('ROLE_ADMIN', 1);
+define('ROLE_MANAGER', 2);
+define('ROLE_USER', 3);
+define('ROLE_GUEST', 4);
+
+define('APP_NAME', 'webcal');
+define('URL_APP_PREFIX_RELATIVE', 'webcal');
+define('URL_APP_PREFIX', '/webcal');
+$port = intval($_SERVER['SERVER_PORT']);
+$port = $port == 0 || $port == 80 ? '' : ":$port";
+define('URL_DOMAIN_APP_PREFIX', $_SERVER['HTTP_HOST'] . "$port" . URL_APP_PREFIX);
+define('FULL_URL_APP_PREFIX', $_SERVER['REQUEST_SCHEME'] . '://' . URL_DOMAIN_APP_PREFIX);
+
+function path()
+{
+ return ROOT . implode(DS, array_values(func_get_args()));
+}
+
+require (path('lib', 'native', 'config.php'));
+require (path('config', 'sys', 'main.php'));
+require (path('config', 'sys', 'loader.php'));
+require (path('config', 'sys', 'error.php'));
+
+spl_autoload_register('autoloader');
+
+register_shutdown_function(function () {
+ $error = error_get_last();
+
+ if (! is_null($error)) {
+ LN\Error::getInstance()->errorHandler($error['type'], $error['message'], $error['file'], $error['line'], debug_backtrace());
+ }
+
+ session_write_close();
+
+ if (LN\Config::get('main_sys')['profiler']['logAtShutdown']) {
+ LN\Profiler::getInstance()->end();
+ }
+});
+define('AJAX', LN\Tool::isAJAX());
+define('DOMAIN', LN\Config::getHostConfig('main_sys:domain', 'covid.infeos.eu'));
+
+// If changed: change root.definitions.php also!
+LN\SemaphorePool::init(array(
+ "singleton" => 47
+));
+
+LN\Error::getInstance();
+
+LN\Session::getInstance();
+if (LN\Config::get('main_sys')['autoLogin']) {
+ $_SESSION['roleID'] = ROLE_MANAGER;
+ $_SESSION['userID'] = 4;
+ $_SESSION['shortname'] = 'wk';
+}
+
+LN\Bootstrap::getInstance();
+
+?>
--- /dev/null
+../../../skeleton/public/js/common
\ No newline at end of file
--- /dev/null
+../../../skeleton/public/js/vendor
\ No newline at end of file
--- /dev/null
+/generator.root.def.php
+/rungenerator.php
--- /dev/null
+../../../skeleton/tools/generator/templates
\ No newline at end of file
--- /dev/null
+module Terms Term
+ option.module use:terms.snippets.php
+ option.module project:webcal
+ option.module type:backend
+
+ dbtable terms term
+ dbfield term_id "Id" int primary
+ dbfield term_name "Bezeichnung" text size:32 secondary
+ dbfield term_date "Datum" datetime
+ dbfield term_info "Bemerkungen" text size:4000 rows:3
+ option.dbtable default.order:term_date
+ end.dbtable
+
+ dbtable userterms userterm
+ option.dbtable make.sql
+ dbfield term_id "Id" int primary
+ dbfield userterm_term " " int ref:terms.term_id
+ dbfield userterm_user " " int ref:loginusers.user_id
+ end.dbtable
+
+ dbtable loginusers user
+ dbfield user_id "Id" int primary
+ dbfield user_name "Kennung" text size:200 secondary notnull unique width:2:4
+ dbfield user_displayname "Anzeigename" text size:32 notnull unique width:2:4
+ dbfield user_shortname "Kürzel" text size:20 notnull unique width:2:4
+ dbfield user_login "E-Mail" text size:100 notnull unique width:2:4
+ dbfield user_gender "Geschlecht" text size:1 notnull field.option:undef combobox width:2:4
+ dbfield user_status "Status" int ref:sysflags.sysflag_id field.option:undef width:2:4
+ dbfield user_info "Info" text size:128 width:2:10
+ dbfield role_id "Rolle" int ref:roles.role_id field.option:undef width:2:4
+ dbfield user_hourssat "Stunden Sa" float width:2:1
+ dbfield user_hourssun "So" float width:1:1
+ dbfield user_hoursmon "Stunden Mo" float width:2:1
+ dbfield user_hourstue "Di" float width:1:1
+ dbfield user_hourswed "Mi" float width:1:1
+ dbfield user_hoursthu "Do" float width:1:1
+ dbfield user_hoursfri "Fr" float width:1:1
+ dbfield user_pass "Passwort" text size:200 hidden
+ dbfield user_token "Token" text size:100 hidden
+ dbfield user_tokenstamp "Tokenzeit" datetime hidden
+ dbfield changed "Geändert" datetime update now hidden
+ dbfield changedby "Geändert von" text size:20 auto.user hidden
+ dbfield created "Erzeugt" datetime update now hidden
+ dbfield createdby "Erzeugt von" text size:20 auto.user hidden
+ end.dbtable
+
+ page base "Termine"
+ option.page sql.getall:PHP_HOOK_SQL_GET_ALL
+ option.page sql.getall.count:PHP_HOOK_SQL_GET_ALL_COUNT
+ section panel.fields
+ empty.line
+ if role in 1,2
+ button buttonNew "Neuer Termin" 0 12 redirect:*.new
+ empty.line
+ end.if
+ end.section
+ section table
+ option.table viewed:term_id:term_date:term_name:members:term_info
+ option.table sorted:term_id:term_date:term_name
+ option.table heads:"Id":"Datum":"Bezeichnung":"Platzanweiser":"Bemerkung"
+ option.table edit.by.dialog
+ option.table delete.by.dialog
+ end.section
+ end.page
+
+ page new "Neuer Termin"
+ section panel.fieldgroup
+ fill.from.db.table terms
+ button buttonSave "Speichern" 2 4 save.and.edit
+ button buttonClose "Schließen" 2 4 close
+ end.section
+ end.page
+
+ page edit "Termin ändern"
+ section panel.fieldgroup
+ dbreference term_name 2 4
+ dbreference term_date 2 4
+ dbreference term_info 2 10
+ field members "Platzanweiser" text 2 10 rows:3 readonly no.storage
+ field member "Ändern" int 2 4 combobox undef no.storage
+ button buttonAdd "Hinzufügen" 0 3 none
+ button buttonSub "Entfernen" 0 3 none
+ empty.line
+ if role in 1,2
+ button buttonSave "Speichern" 2 4 save.and.exit
+ end.if
+ if role in 3,4
+ filler 6
+ end.if
+ button buttonClose "Schließen" 0 3 close
+ end.section
+ end.page
+
+ page delete "Termin löschen"
+ section panel.fieldgroup
+ fill.from.db.table terms
+ button buttonDelete "Löschen" 2 4 delete.direct
+ button buttonClose "Schließen" 2 4 close
+ end.section
+ end.page
+
+end.module
--- /dev/null
+PHP_HOOK_SQL_GET_ALL:
+SELECT
+ tt.*,'dummy' as members
+FROM
+ terms tt
+WHERE
+ tt.deletedat is null
+
+PHP_HOOK_SQL_GET_ALL_COUNT:
+SELECT
+ count(*)
+FROM
+ terms tt
+WHERE
+ tt.deletedat is null
+
+PHP_HOOK_EDIT_FIELDSET_COMPLETE_FIELDS:
+ $common->fillUserCombobox($fieldMember, [1, 27, 30], false);
+
+PHP_HOOK_EDIT_VALIDATED:
+ if (\array_key_exists('buttonAdd', $_POST)){
+ $locals->addMember($fieldMember->value, $termID);
+ } elseif (\array_key_exists('buttonSub', $_POST)){
+ $locals->subMember($fieldMember->value, $termID);
+ }
+
+PHP_HOOK_EDIT_HTML:
+ $fieldMembers->value = $locals->membersOfTerm($termID);
+
+PHP_HOOK_BASE_TABLE_ROW_ADAPTION:
+ $rec['members'] = $locals->membersOfTerm($rec['term_id']);
+
+PHP_HOOK_EDIT_FIRST_CALL_EARLY:
+ $fieldMember->value = $_SESSION['userID'];
+
+PHP_HOOK_BASE_TABLE_RECORDS_ADAPTION:
+ $deleteAllowed = $_SESSION['roleID'] <= 2;
+
+END:
\ No newline at end of file
--- /dev/null
+<html>\r
+ <head>\r
+ <style>\r
+ body {\r
+ font-family: calibri, sans-serif;\r
+ color: maroon;\r
+ }\r
+ </style>\r
+ </head>\r
+ <body>\r
+ <p><strong>[&&SALUTATION] [&&PERSON],</strong></p>\r
+ <p>Ihr neues Passwort lautet: <strong>[&&PASSWORD]</strong></p> \r
+ <p>Mit freundlichen Grüßen</p>\r
+ <p>[&&EMAIL_SIGNATURE]</p>\r
+ </body>\r
+</html>
\ No newline at end of file
--- /dev/null
+[&&SALUTATION] [&&PERSON],\r
+\r
+Ihr neues Passwort lautet: [&&PASSWORD]\r
+\r
+Mit freundlichen Grüßen\r
+\r
+[&&EMAIL_SIGNATURE]
\ No newline at end of file
--- /dev/null
+<html>\r
+ <head>\r
+ <style>\r
+ body {\r
+ font-family: calibri, sans-serif;\r
+ color: maroon;\r
+ }\r
+ </style>\r
+ </head>\r
+ <body>\r
+ <p><strong>[&&SALUTATION] [&&PERSON],</strong></p>\r
+ <p>Sie wurden auf dem Webportal - TimeTracking angelegt: <a href="[&&LINK]">[&&LINK]</a></p>\r
+ <table cellpadding="10">\r
+ <tr>\r
+ <td>Benutzername:</td>\r
+ <td>[&&EMAIL]</td>\r
+ </tr>\r
+ <tr>\r
+ <td>Passwort:</td>\r
+ <td>[&&PASSWORD]</td>\r
+ </tr>\r
+ </table>\r
+ <p>Mit freundlichen Grüßen</p>\r
+ <p>[&&EMAIL_SIGNATURE]</p>\r
+ </body>\r
+</html>
\ No newline at end of file
--- /dev/null
+[&&SALUTATION] [&&PERSON],\r
+\r
+Sie wurden auf dem Webportal - TimeTracking angelegt: [&&LINK]\r
+\r
+Benutzername: [&&EMAIL]\r
+Passwort: [&&PASSWORD]\r
+\r
+Mit freundlichen Grüßen\r
+\r
+[&&EMAIL_SIGNATURE]
\ No newline at end of file
--- /dev/null
+<html>\r
+ <head>\r
+ <style>\r
+ body {\r
+ font-family: calibri, sans-serif;\r
+ color: maroon;\r
+ }\r
+ </style>\r
+ </head>\r
+ <body>\r
+ <p><strong>[&&SALUTATION] [&&PERSON],</strong></p>\r
+ <p>klicken Sie bitte auf den Link um Ihr Passwort zu ändern. Die Sitzung ist in <strong>[&&TIMEOUT]</strong> Minuten abgelaufen:</p>\r
+ <p><strong><a href="[&&LINK]">Passwort ändern</a></strong></p>\r
+ <p>Mit freundlichen Grüßen</p>\r
+ <p>[&&EMAIL_SIGNATURE]</p>\r
+ </body>\r
+</html>
\ No newline at end of file
--- /dev/null
+[&&SALUTATION] [&&PERSON],\r
+\r
+klicken Sie bitte auf den Link um Ihr Passwort zu ändern. Die Sitzung ist [&&TIMEOUT] Minuten abgelaufen:\r
+\r
+[&&LINK]\r
+\r
+Mit freundlichen Grüßen\r
+\r
+[&&EMAIL_SIGNATURE]
\ No newline at end of file