]> gitweb.hamatoma.de Git - webcal.git/commitdiff
WCL2020.05.24.00: Start
authorWinfried Kappeler <winfried.kappeler@infeos.eu>
Sun, 24 May 2020 22:34:36 +0000 (00:34 +0200)
committerWinfried Kappeler <winfried.kappeler@infeos.eu>
Sun, 24 May 2020 22:34:36 +0000 (00:34 +0200)
* lauffähige Version

71 files changed:
.buildpath [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.project [new file with mode: 0644]
app/common [new symlink]
app/configurations [new symlink]
app/locals/build/common/locals.common.config.php [new file with mode: 0644]
app/locals/build/common/locals.common.snippets.html [new file with mode: 0644]
app/locals/build/common/locals.frame.snippets.html [new file with mode: 0644]
app/locals/locals.controller.php [new file with mode: 0644]
app/locals/locals.model.php [new file with mode: 0644]
app/locals/locals.service.php [new file with mode: 0644]
app/locals/locals.view.php [new file with mode: 0644]
app/locals/sql/locals.snippets.sql [new file with mode: 0644]
app/login [new symlink]
app/logs [new symlink]
app/overview [new symlink]
app/roles [new symlink]
app/sessions [new symlink]
app/settings [new symlink]
app/starters [new symlink]
app/sysflags [new symlink]
app/terms/build/base/terms.base.config.php [new file with mode: 0644]
app/terms/build/base/terms.base.snippets.html [new file with mode: 0644]
app/terms/build/common/terms.common.config.php [new file with mode: 0644]
app/terms/build/common/terms.common.snippets.html [new file with mode: 0644]
app/terms/build/delete/terms.delete.config.php [new file with mode: 0644]
app/terms/build/delete/terms.delete.snippets.html [new file with mode: 0644]
app/terms/build/edit/terms.edit.config.php [new file with mode: 0644]
app/terms/build/edit/terms.edit.snippets.html [new file with mode: 0644]
app/terms/build/new/terms.new.config.php [new file with mode: 0644]
app/terms/build/new/terms.new.snippets.html [new file with mode: 0644]
app/terms/sql/terms.snippets.sql [new file with mode: 0644]
app/terms/sql/terms.sql [new file with mode: 0644]
app/terms/terms.controller.php [new file with mode: 0644]
app/terms/terms.model.php [new file with mode: 0644]
app/terms/terms.view.php [new file with mode: 0644]
app/users [new symlink]
config/changes/skeletonversion.php [new symlink]
config/changes/version.php [new file with mode: 0644]
config/sys/.gitignore [new file with mode: 0644]
config/sys/error.php [new symlink]
config/sys/loader.php [new symlink]
db/.gitignore [new file with mode: 0644]
lib [new symlink]
public/css/page/common/global.css [new symlink]
public/css/page/common/jquery.datetimepicker.css [new symlink]
public/css/page/common/local.css [new file with mode: 0644]
public/css/page/common/pdf.global.css [new symlink]
public/css/page/overview/overview.global.css [new symlink]
public/css/page/overview/overview.local.css [new file with mode: 0644]
public/css/vendor [new symlink]
public/fonts [new symlink]
public/icon/famfamfam [new symlink]
public/icon/favicon.ico [new symlink]
public/icon/kirchturm.ico [new file with mode: 0644]
public/icon/msg [new symlink]
public/icon/start [new symlink]
public/img/vendor [new symlink]
public/index.php [new file with mode: 0644]
public/js/common [new symlink]
public/js/vendor [new symlink]
tools/generator/.gitignore [new file with mode: 0644]
tools/generator/templates [new symlink]
tools/generator/terms.module [new file with mode: 0644]
tools/generator/terms.snippets.php [new file with mode: 0644]
tpl/email/newPwd.html [new file with mode: 0644]
tpl/email/newPwd.txt [new file with mode: 0644]
tpl/email/newUser.html [new file with mode: 0644]
tpl/email/newUser.txt [new file with mode: 0644]
tpl/email/pwdForgotten.html [new file with mode: 0644]
tpl/email/pwdForgotten.txt [new file with mode: 0644]

diff --git a/.buildpath b/.buildpath
new file mode 100644 (file)
index 0000000..8bcb4b5
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<buildpath>
+       <buildpathentry kind="src" path=""/>
+       <buildpathentry kind="con" path="org.eclipse.php.core.LANGUAGE"/>
+</buildpath>
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..c04993d
--- /dev/null
@@ -0,0 +1,2 @@
+/Deploy.sh
+/do.not.deploy.list
diff --git a/.project b/.project
new file mode 100644 (file)
index 0000000..4053352
--- /dev/null
+++ b/.project
@@ -0,0 +1,28 @@
+<?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>
diff --git a/app/common b/app/common
new file mode 120000 (symlink)
index 0000000..32c2db8
--- /dev/null
@@ -0,0 +1 @@
+../../skeleton/app/common
\ No newline at end of file
diff --git a/app/configurations b/app/configurations
new file mode 120000 (symlink)
index 0000000..1fba546
--- /dev/null
@@ -0,0 +1 @@
+../../skeleton/app/configurations
\ No newline at end of file
diff --git a/app/locals/build/common/locals.common.config.php b/app/locals/build/common/locals.common.config.php
new file mode 100644 (file)
index 0000000..47e1c1a
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+use \app\common as TC;
+
+$configArray = array(
+    'local_js' => '',
+    'local_css' => '',
+    'local_pageType' => 'base',
+    'local_title' => 'Lokales',
+);
diff --git a/app/locals/build/common/locals.common.snippets.html b/app/locals/build/common/locals.common.snippets.html
new file mode 100644 (file)
index 0000000..4ec94e0
--- /dev/null
@@ -0,0 +1,15 @@
+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:
diff --git a/app/locals/build/common/locals.frame.snippets.html b/app/locals/build/common/locals.frame.snippets.html
new file mode 100644 (file)
index 0000000..293fe12
--- /dev/null
@@ -0,0 +1,259 @@
+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! &nbsp;
+       <select id="pagingSize" name="pagingSize">###opt_pagingSize###
+       </select>&nbsp;###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">&nbsp;</div>
+    <div  class="col-md-4"><button id="buttonLeavePage">Daten verwerfen</button></div>
+    <div  class="col-md-2">&nbsp;</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:
diff --git a/app/locals/locals.controller.php b/app/locals/locals.controller.php
new file mode 100644 (file)
index 0000000..ba53ec3
--- /dev/null
@@ -0,0 +1,82 @@
+<?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;
+    }
+}
diff --git a/app/locals/locals.model.php b/app/locals/locals.model.php
new file mode 100644 (file)
index 0000000..571bb2b
--- /dev/null
@@ -0,0 +1,79 @@
+<?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;
+    }
+}
diff --git a/app/locals/locals.service.php b/app/locals/locals.service.php
new file mode 100644 (file)
index 0000000..46bcfa0
--- /dev/null
@@ -0,0 +1,168 @@
+<?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";
+        }
+    }
+}
diff --git a/app/locals/locals.view.php b/app/locals/locals.view.php
new file mode 100644 (file)
index 0000000..ff757d9
--- /dev/null
@@ -0,0 +1,32 @@
+<?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
+    }
+
+}
diff --git a/app/locals/sql/locals.snippets.sql b/app/locals/sql/locals.snippets.sql
new file mode 100644 (file)
index 0000000..6ff8c6d
--- /dev/null
@@ -0,0 +1,26 @@
+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
diff --git a/app/login b/app/login
new file mode 120000 (symlink)
index 0000000..dd40685
--- /dev/null
+++ b/app/login
@@ -0,0 +1 @@
+../../skeleton/app/login
\ No newline at end of file
diff --git a/app/logs b/app/logs
new file mode 120000 (symlink)
index 0000000..30e3dc2
--- /dev/null
+++ b/app/logs
@@ -0,0 +1 @@
+../../skeleton/app/logs
\ No newline at end of file
diff --git a/app/overview b/app/overview
new file mode 120000 (symlink)
index 0000000..be7e20d
--- /dev/null
@@ -0,0 +1 @@
+../../skeleton/app/overview
\ No newline at end of file
diff --git a/app/roles b/app/roles
new file mode 120000 (symlink)
index 0000000..9ac17a1
--- /dev/null
+++ b/app/roles
@@ -0,0 +1 @@
+../../skeleton/app/roles
\ No newline at end of file
diff --git a/app/sessions b/app/sessions
new file mode 120000 (symlink)
index 0000000..0b0850d
--- /dev/null
@@ -0,0 +1 @@
+../../skeleton/app/sessions
\ No newline at end of file
diff --git a/app/settings b/app/settings
new file mode 120000 (symlink)
index 0000000..fd009af
--- /dev/null
@@ -0,0 +1 @@
+../../skeleton/app/settings
\ No newline at end of file
diff --git a/app/starters b/app/starters
new file mode 120000 (symlink)
index 0000000..83e3139
--- /dev/null
@@ -0,0 +1 @@
+../../skeleton/app/starters
\ No newline at end of file
diff --git a/app/sysflags b/app/sysflags
new file mode 120000 (symlink)
index 0000000..f182201
--- /dev/null
@@ -0,0 +1 @@
+../../skeleton/app/sysflags
\ No newline at end of file
diff --git a/app/terms/build/base/terms.base.config.php b/app/terms/build/base/terms.base.config.php
new file mode 100644 (file)
index 0000000..c0906e4
--- /dev/null
@@ -0,0 +1,15 @@
+<?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!' => ''
+);
diff --git a/app/terms/build/base/terms.base.snippets.html b/app/terms/build/base/terms.base.snippets.html
new file mode 100644 (file)
index 0000000..3f3f14a
--- /dev/null
@@ -0,0 +1,58 @@
+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:
diff --git a/app/terms/build/common/terms.common.config.php b/app/terms/build/common/terms.common.config.php
new file mode 100644 (file)
index 0000000..4d0e492
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+use \app\common as TC;
+
+
+
+$configArray = array(
+    'local_js' => '',
+    'local_css' => '',
+    'data' => 'Daten',     'parent_title' => 'Termine',
+
+        // PHP_HOOK_CONFIG_COMMON
+    '!EoA!' => ''
+);
diff --git a/app/terms/build/common/terms.common.snippets.html b/app/terms/build/common/terms.common.snippets.html
new file mode 100644 (file)
index 0000000..6e42e03
--- /dev/null
@@ -0,0 +1,8 @@
+SNIPPET_FIELDS:
+<!-- page.common -->
+<!-- end.page.common -->
+
+SNIPPET_DUMMY_FOR_HOOK:
+<!-- PHP_HOOK_HTML_SNIPPETS not used-->
+
+END:
diff --git a/app/terms/build/delete/terms.delete.config.php b/app/terms/build/delete/terms.delete.config.php
new file mode 100644 (file)
index 0000000..7e8df43
--- /dev/null
@@ -0,0 +1,14 @@
+<?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!' => ''
+);
diff --git a/app/terms/build/delete/terms.delete.snippets.html b/app/terms/build/delete/terms.delete.snippets.html
new file mode 100644 (file)
index 0000000..2187d9b
--- /dev/null
@@ -0,0 +1,27 @@
+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:
diff --git a/app/terms/build/edit/terms.edit.config.php b/app/terms/build/edit/terms.edit.config.php
new file mode 100644 (file)
index 0000000..afe55fe
--- /dev/null
@@ -0,0 +1,14 @@
+<?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!' => ''
+);
diff --git a/app/terms/build/edit/terms.edit.snippets.html b/app/terms/build/edit/terms.edit.snippets.html
new file mode 100644 (file)
index 0000000..01c3cbe
--- /dev/null
@@ -0,0 +1,41 @@
+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:
diff --git a/app/terms/build/new/terms.new.config.php b/app/terms/build/new/terms.new.config.php
new file mode 100644 (file)
index 0000000..da46f2f
--- /dev/null
@@ -0,0 +1,14 @@
+<?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!' => ''
+);
diff --git a/app/terms/build/new/terms.new.snippets.html b/app/terms/build/new/terms.new.snippets.html
new file mode 100644 (file)
index 0000000..02c155d
--- /dev/null
@@ -0,0 +1,27 @@
+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:
diff --git a/app/terms/sql/terms.snippets.sql b/app/terms/sql/terms.snippets.sql
new file mode 100644 (file)
index 0000000..bbac8f9
--- /dev/null
@@ -0,0 +1,85 @@
+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:
diff --git a/app/terms/sql/terms.sql b/app/terms/sql/terms.sql
new file mode 100644 (file)
index 0000000..19e0612
--- /dev/null
@@ -0,0 +1,64 @@
+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) 
+;
+
diff --git a/app/terms/terms.controller.php b/app/terms/terms.controller.php
new file mode 100644 (file)
index 0000000..0d31369
--- /dev/null
@@ -0,0 +1,23 @@
+<?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
diff --git a/app/terms/terms.model.php b/app/terms/terms.model.php
new file mode 100644 (file)
index 0000000..1e154c9
--- /dev/null
@@ -0,0 +1,205 @@
+<?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
diff --git a/app/terms/terms.view.php b/app/terms/terms.view.php
new file mode 100644 (file)
index 0000000..7062541
--- /dev/null
@@ -0,0 +1,622 @@
+<?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;
+    }
+}
+?>
+
diff --git a/app/users b/app/users
new file mode 120000 (symlink)
index 0000000..e59009f
--- /dev/null
+++ b/app/users
@@ -0,0 +1 @@
+../../skeleton/app/users
\ No newline at end of file
diff --git a/config/changes/skeletonversion.php b/config/changes/skeletonversion.php
new file mode 120000 (symlink)
index 0000000..859d9fe
--- /dev/null
@@ -0,0 +1 @@
+../../../skeleton/config/changes/skeletonversion.php
\ No newline at end of file
diff --git a/config/changes/version.php b/config/changes/version.php
new file mode 100644 (file)
index 0000000..e6db836
--- /dev/null
@@ -0,0 +1,22 @@
+<?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 : '???' );
+?>
diff --git a/config/sys/.gitignore b/config/sys/.gitignore
new file mode 100644 (file)
index 0000000..8a71748
--- /dev/null
@@ -0,0 +1 @@
+/main.php
diff --git a/config/sys/error.php b/config/sys/error.php
new file mode 120000 (symlink)
index 0000000..4c84964
--- /dev/null
@@ -0,0 +1 @@
+../../../skeleton/config/sys/error.php
\ No newline at end of file
diff --git a/config/sys/loader.php b/config/sys/loader.php
new file mode 120000 (symlink)
index 0000000..04111f1
--- /dev/null
@@ -0,0 +1 @@
+../../../skeleton/config/sys/loader.php
\ No newline at end of file
diff --git a/db/.gitignore b/db/.gitignore
new file mode 100644 (file)
index 0000000..31cbb99
--- /dev/null
@@ -0,0 +1 @@
+/webcal.sql.gz
diff --git a/lib b/lib
new file mode 120000 (symlink)
index 0000000..183f052
--- /dev/null
+++ b/lib
@@ -0,0 +1 @@
+../skeleton/lib
\ No newline at end of file
diff --git a/public/css/page/common/global.css b/public/css/page/common/global.css
new file mode 120000 (symlink)
index 0000000..046e7c4
--- /dev/null
@@ -0,0 +1 @@
+../../../../../skeleton/public/css/page/common/global.css
\ No newline at end of file
diff --git a/public/css/page/common/jquery.datetimepicker.css b/public/css/page/common/jquery.datetimepicker.css
new file mode 120000 (symlink)
index 0000000..4851126
--- /dev/null
@@ -0,0 +1 @@
+../../../../../skeleton/public/css/page/common/jquery.datetimepicker.css
\ No newline at end of file
diff --git a/public/css/page/common/local.css b/public/css/page/common/local.css
new file mode 100644 (file)
index 0000000..de23d49
--- /dev/null
@@ -0,0 +1,133 @@
+@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
diff --git a/public/css/page/common/pdf.global.css b/public/css/page/common/pdf.global.css
new file mode 120000 (symlink)
index 0000000..88c8b9b
--- /dev/null
@@ -0,0 +1 @@
+../../../../../skeleton/public/css/page/common/pdf.global.css
\ No newline at end of file
diff --git a/public/css/page/overview/overview.global.css b/public/css/page/overview/overview.global.css
new file mode 120000 (symlink)
index 0000000..5e78b45
--- /dev/null
@@ -0,0 +1 @@
+../../../../../skeleton/public/css/page/overview/overview.global.css
\ No newline at end of file
diff --git a/public/css/page/overview/overview.local.css b/public/css/page/overview/overview.local.css
new file mode 100644 (file)
index 0000000..ffd5d70
--- /dev/null
@@ -0,0 +1,13 @@
+@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
diff --git a/public/css/vendor b/public/css/vendor
new file mode 120000 (symlink)
index 0000000..f174506
--- /dev/null
@@ -0,0 +1 @@
+../../../skeleton/public/css/vendor
\ No newline at end of file
diff --git a/public/fonts b/public/fonts
new file mode 120000 (symlink)
index 0000000..152b0c7
--- /dev/null
@@ -0,0 +1 @@
+../../skeleton/public/fonts
\ No newline at end of file
diff --git a/public/icon/famfamfam b/public/icon/famfamfam
new file mode 120000 (symlink)
index 0000000..21973d5
--- /dev/null
@@ -0,0 +1 @@
+../../../skeleton/public/icon/famfamfam
\ No newline at end of file
diff --git a/public/icon/favicon.ico b/public/icon/favicon.ico
new file mode 120000 (symlink)
index 0000000..b2d1e8a
--- /dev/null
@@ -0,0 +1 @@
+kirchturm.ico
\ No newline at end of file
diff --git a/public/icon/kirchturm.ico b/public/icon/kirchturm.ico
new file mode 100644 (file)
index 0000000..c0bacdc
Binary files /dev/null and b/public/icon/kirchturm.ico differ
diff --git a/public/icon/msg b/public/icon/msg
new file mode 120000 (symlink)
index 0000000..a474549
--- /dev/null
@@ -0,0 +1 @@
+../../../skeleton/public/icon/msg
\ No newline at end of file
diff --git a/public/icon/start b/public/icon/start
new file mode 120000 (symlink)
index 0000000..fca59e8
--- /dev/null
@@ -0,0 +1 @@
+../../../skeleton/public/icon/start
\ No newline at end of file
diff --git a/public/img/vendor b/public/img/vendor
new file mode 120000 (symlink)
index 0000000..2408d6c
--- /dev/null
@@ -0,0 +1 @@
+../../../skeleton/public/img/vendor
\ No newline at end of file
diff --git a/public/index.php b/public/index.php
new file mode 100644 (file)
index 0000000..94ed55f
--- /dev/null
@@ -0,0 +1,72 @@
+<?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();
+
+?>
diff --git a/public/js/common b/public/js/common
new file mode 120000 (symlink)
index 0000000..58acc78
--- /dev/null
@@ -0,0 +1 @@
+../../../skeleton/public/js/common
\ No newline at end of file
diff --git a/public/js/vendor b/public/js/vendor
new file mode 120000 (symlink)
index 0000000..4f48350
--- /dev/null
@@ -0,0 +1 @@
+../../../skeleton/public/js/vendor
\ No newline at end of file
diff --git a/tools/generator/.gitignore b/tools/generator/.gitignore
new file mode 100644 (file)
index 0000000..62ad278
--- /dev/null
@@ -0,0 +1,2 @@
+/generator.root.def.php
+/rungenerator.php
diff --git a/tools/generator/templates b/tools/generator/templates
new file mode 120000 (symlink)
index 0000000..3dfdcb1
--- /dev/null
@@ -0,0 +1 @@
+../../../skeleton/tools/generator/templates
\ No newline at end of file
diff --git a/tools/generator/terms.module b/tools/generator/terms.module
new file mode 100644 (file)
index 0000000..ddeba02
--- /dev/null
@@ -0,0 +1,102 @@
+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
diff --git a/tools/generator/terms.snippets.php b/tools/generator/terms.snippets.php
new file mode 100644 (file)
index 0000000..8aecdc0
--- /dev/null
@@ -0,0 +1,39 @@
+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
diff --git a/tpl/email/newPwd.html b/tpl/email/newPwd.html
new file mode 100644 (file)
index 0000000..f9c805b
--- /dev/null
@@ -0,0 +1,16 @@
+<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
diff --git a/tpl/email/newPwd.txt b/tpl/email/newPwd.txt
new file mode 100644 (file)
index 0000000..11deb5f
--- /dev/null
@@ -0,0 +1,7 @@
+[&&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
diff --git a/tpl/email/newUser.html b/tpl/email/newUser.html
new file mode 100644 (file)
index 0000000..e66bbb0
--- /dev/null
@@ -0,0 +1,26 @@
+<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
diff --git a/tpl/email/newUser.txt b/tpl/email/newUser.txt
new file mode 100644 (file)
index 0000000..d95eb4f
--- /dev/null
@@ -0,0 +1,10 @@
+[&&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
diff --git a/tpl/email/pwdForgotten.html b/tpl/email/pwdForgotten.html
new file mode 100644 (file)
index 0000000..6e29ef1
--- /dev/null
@@ -0,0 +1,17 @@
+<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
diff --git a/tpl/email/pwdForgotten.txt b/tpl/email/pwdForgotten.txt
new file mode 100644 (file)
index 0000000..61a9a87
--- /dev/null
@@ -0,0 +1,9 @@
+[&&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