diff --git a/mbrs/Dockerfile b/mbrs/Dockerfile new file mode 100644 index 0000000..92e8003 --- /dev/null +++ b/mbrs/Dockerfile @@ -0,0 +1,32 @@ +ARG MRBS_RELEASE=v1.11.1 + +FROM php:8.2.7-apache + +RUN a2enmod rewrite +RUN a2enmod ssl +RUN apt-get update && apt-get install libldap2-dev libicu-dev zlib1g-dev libpng-dev ldap-utils -y +RUN docker-php-ext-install mysqli +RUN apt-get update && apt-get install -y locales && \ + sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen && \ + sed -i 's/^# *\(en_GB.UTF-8\)/\1/' /etc/locale.gen && \ + sed -i 's/^# *\(es_ES.UTF-8\)/\1/' /etc/locale.gen && \ + locale-gen + +RUN \ + echo "**** fetch mrbs ****" && \ + rm -rf /var/www/html && \ + mkdir -p /var/www/html && \ + curl -o /tmp/mrbs.tar.gz -L \ + "https://github.com/meeting-room-booking-system/mrbs-code/archive/${MRBS_RELEASE}.tar.gz" && \ + echo "**** extract only folder 'web' ****" && \ + tar -C /var/www/html --strip-components=2 -zxvf /tmp/mrbs.tar.gz $(tar --exclude="*/*" -tf /tmp/mrbs.tar.gz)web && \ + mkdir -p /usr/share/mrbs && \ + tar -C /usr/share/mrbs --wildcards --strip-components=1 -zxvf /tmp/mrbs.tar.gz $(tar --exclude="*/*" -tf /tmp/mrbs.tar.gz)tables.*.sql && \ + echo "**** cleanup ****" && \ + rm -rf /tmp/* + +RUN docker-php-ext-install mysqli pdo pdo_mysql ldap intl gd + +COPY Themes/verdnatura /var/www/html/web/Themes + + diff --git a/mbrs/Themes/verdnatura/footer.inc b/mbrs/Themes/verdnatura/footer.inc new file mode 100644 index 0000000..d28163a --- /dev/null +++ b/mbrs/Themes/verdnatura/footer.inc @@ -0,0 +1,10 @@ +\n"; // closing the contents div, opened in print_theme_header() + echo "\n"; + echo "\n"; +} + \ No newline at end of file diff --git a/mbrs/Themes/verdnatura/header.inc b/mbrs/Themes/verdnatura/header.inc new file mode 100644 index 0000000..4061a1f --- /dev/null +++ b/mbrs/Themes/verdnatura/header.inc @@ -0,0 +1,660 @@ +\n"; + + echo "\n"; + + // Set IE=edge so that IE10 will display MRBS properly, even if compatibility mode is used + // on the browser. If we don't do this then MRBS will treat IE10 as an unsupported browser + // when compatibility mode is turned on, potentially confusing users who may have forgotten + // that they are using compatibility mode. Unfortunately we can't set IE=edge in the header, + // which is where we would normally do it, because then we won't be able to detect IE9 using + // conditional comments. So we have to do it in a tag, after the conditional comments + // around the tags. + echo "\n"; + + // Improve scaling on mobile devices + echo "\n"; + + if (!$simple) + { + // Add the CSRF token so that JavaScript can use it + echo "\n"; + } + + echo "\n"; + + if (($refresh_rate != 0) && (this_page(false, '.php') == 'index')) + { + // If we're using JavaScript we'll do the refresh by getting a new + // table using Ajax requests, which means we only have to download + // the table not the whole page each time + echo "\n"; + } + + echo "" . get_vocab("mrbs") . "\n"; + + require_once MRBS_ROOT . "/style.inc"; + + if (!$simple) + { + require_once MRBS_ROOT . "/js.inc"; + } + + echo "\n"; +} + + +// Print the basic site information. This function is used for all headers, including +// the simple header, and so mustn't require any database access. +function print_header_site_info() +{ + global $mrbs_company, + $mrbs_company_url, + $mrbs_company_logo, + $mrbs_company_more_info; + + // Company logo, with a link to the company + if (!empty($mrbs_company_logo)) + { + echo "
\n"; + if (!empty($mrbs_company_url)) + { + echo ''; + } + // Suppress error messages in case the logo is a URL, in which case getimagesize() can + // fail for any number of reasons, eg (a) allow_url_fopen is not enabled in php.ini or + // (b) "SSL operation failed with code 1. OpenSSL Error messages: error:1416F086:SSL + // routines:tls_process_server_certificate:certificate verify failed". As the image + // size is not essential we'll just carry on. + $logo_size = @getimagesize($mrbs_company_logo); + echo '' . htmlspecialchars($mrbs_company) . ''; + + if (!empty($mrbs_company_url)) + { + echo "\n"; + } + echo "
\n"; + } + + // Company name, any extra company info and MRBS + echo "
\n"; + if (!empty($mrbs_company_url)) + { + echo ''; + } + echo '' . htmlspecialchars($mrbs_company) . ''; + if (!empty($mrbs_company_url)) + { + echo "\n"; + } + if (!empty($mrbs_company_more_info)) + { + // Do not put $mrbs_company_more_info through htmlspecialchars() as it is + // trusted and allowed to contain HTML. + echo "$mrbs_company_more_info\n"; + } + echo '' . get_vocab('mrbs') . "\n"; + echo "
\n"; +} + + +function print_goto_date($context) +{ + global $multisite, $site; + + if (!checkAuthorised('index.php', true)) + { + // Don't show the goto box if the user isn't allowed to view the calendar + return; + } + + $form = new Form(); + + $form_id = 'form_nav'; + + $form->setAttributes(array('id' => $form_id, + 'class' => 'js_hidden', + 'method' => 'get', + 'action' => multisite('index.php'))) + ->addHiddenInput('view', $context['view']); + + if (isset($context['area'])) + { + $form->addHiddenInput('area', $context['area']); + } + + if (isset($room)) + { + $form->addHiddenInput('room', $context['room']); + } + + if ($multisite && isset($site) && ($site !== '')) + { + $form->addHiddenInput('site', $site); + } + + $input = new ElementInputDate(); + $input->setAttributes(array('name' => 'page_date', + 'value' => format_iso_date($context['year'], $context['month'], $context['day']), + 'aria-label' => get_vocab('goto'), + 'required' => true, + 'data-submit' => $form_id)); + + $form->addElement($input); + + $submit = new ElementInputSubmit(); + $submit->setAttribute('value', get_vocab('goto')); + $form->addElement($submit); + + $form->render(); +} + + +function print_outstanding($query) +{ + $mrbs_user = session()->getCurrentUser(); + + if (!isset($mrbs_user)) + { + return; + } + + // Provide a link to the list of bookings awaiting approval + // (if there are any enabled areas where we require bookings to be approved) + $approval_somewhere = some_area('approval_enabled', TRUE); + if ($approval_somewhere && ($mrbs_user->level > 0)) + { + $n_outstanding = get_entries_n_outstanding($mrbs_user); + + $class = 'notification'; + + if ($n_outstanding > 0) + { + $class .= ' attention'; + } + + echo '$n_outstanding\n"; + } +} + + +function print_menu_items($query) +{ + global $auth, $kiosk_mode_enabled; + + $menu_items = array('help' => 'help.php', + 'report' => 'report.php', + 'import' => 'import.php'); + + if ($kiosk_mode_enabled) + { + $menu_items['kiosk'] = 'kiosk.php'; + } + + $menu_items['rooms'] = 'admin.php'; + + if ($auth['type'] == 'db') + { + $menu_items['user_list'] = 'edit_users.php'; + } + + foreach ($menu_items as $token => $page) + { + // Only print menu items for which the user is allowed to access the page + if (checkAuthorised($page, true)) + { + echo '' . get_vocab($token) . "\n"; + } + } +} + + + +function print_search($context) +{ + if (!checkAuthorised('search.php', true)) + { + // Don't show the search box if the user isn't allowed to search + return; + } + + echo "
\n"; + + $form = new Form(); + + $form->setAttributes(array( + 'id' => 'header_search', + 'method' => 'post', + 'action' => multisite('search.php')) + ) + ->addHiddenInputs(array( + 'view' => $context['view'], + 'year' => $context['year'], + 'month' => $context['month'], + 'day' => $context['day'], + 'from_date' => format_iso_date($context['year'], $context['month'], $context['day']) + ) + ); + + if (!empty($context['area'])) + { + $form->addHiddenInput('area', $context['area']); + } + if (!empty($context['room'])) + { + $form->addHiddenInput('room', $context['room']); + } + + $input = new ElementInputSearch(); + $search_vocab = get_vocab('search'); + + $input->setAttributes(array('name' => 'search_str', + 'placeholder' => $search_vocab, + 'aria-label' => $search_vocab, + 'required' => true)); + + $form->addElement($input); + + $submit = new ElementInputSubmit(); + $submit->setAttributes(array('value' => get_vocab('search_button'), + 'class' => 'js_none')); + $form->addElement($submit); + + $form->render(); + + echo "
\n"; +} + + +// Generate the username link, which gives a report on the user's upcoming bookings. +function print_report_link(User $user) +{ + // If possible, provide a link to the Report page, otherwise the Search page + // and if that's not possible just print the username with no link. (Note that + // the Search page isn't the perfect solution because it searches for any bookings + // containing the search string, not just those created by the user.) + if (checkAuthorised('report.php', true)) + { + $attributes = array('action' => multisite('report.php')); + $hidden_inputs = array('phase' => '2', + 'creatormatch' => $user->username); + } + elseif (checkAuthorised('search.php', true)) + { + $attributes = array('action' => multisite('search.php')); + $hidden_inputs = array('search_str' => $user->username); + } + else + { + echo '' . htmlspecialchars($user->display_name) . ''; + return; + } + + // We're authorised for either Report or Search so print the form. + $form = new Form(); + + $attributes['id'] = 'show_my_entries'; + $attributes['method'] = 'post'; + $form->setAttributes($attributes) + ->addHiddenInputs($hidden_inputs); + + $submit = new ElementInputSubmit(); + $submit->setAttributes(array('title' => get_vocab('show_my_entries'), + 'value' => $user->display_name)); + $form->addElement($submit); + + $form->render(); +} + + +function print_logonoff_button($params, $value) +{ + $form = new Form(); + $form->setAttributes(array('method' => $params['method'], + 'action' => $params['action'])); + + // A Get method will replace the query string in the action URL with a query + // string made up of the hidden inputs. So put any parameters in the action + // query string into hidden inputs. + if (utf8_strtolower($params['method']) == 'get') + { + $query_string = parse_url($params['action'], PHP_URL_QUERY); + if (isset($query_string)) + { + parse_str($query_string, $query_parameters); + $form->addHiddenInputs($query_parameters); + } + } + + // Add the hidden fields + if (isset($params['hidden_inputs'])) + { + $form->addHiddenInputs($params['hidden_inputs']); + } + + // The submit button + $element = new ElementInputSubmit(); + $element->setAttribute('value', $value); + $form->addElement($element); + + $form->render(); +} + + +function print_logon() +{ + if (method_exists(session(), 'getLogonFormParams')) + { + $form_params = session()->getLogonFormParams(); + if (isset($form_params)) + { + print_logonoff_button($form_params, get_vocab('login')); + } + } +} + + +function print_logoff() +{ + if (method_exists(session(), 'getLogoffFormParams')) + { + $form_params = session()->getLogoffFormParams(); + if (isset($form_params)) + { + print_logonoff_button($form_params, get_vocab('logoff')); + } + } +} + +// $context is an associative array indexed by 'view', 'view_all', 'year', 'month', 'day', 'area' and 'room'. +// When $omit_login is true the Login link is omitted. +function print_banner($context, $simple=false, $omit_login=false) +{ + global $kiosk_QR_code, $auth; + + echo '
\n"; + + $vars = array(); + + if (isset($context['view'])) + { + $vars['view'] = $context['view']; + } + if (isset($context['year']) && isset($context['month']) && isset($context['day'])) + { + $vars['page_date'] = format_iso_date($context['year'], $context['month'], $context['day']); + } + if (isset($context['area'])) + { + $vars['area'] = $context['area']; + } + if (isset($context['room'])) + { + $vars['room'] = $context['room']; + } + + $query = http_build_query($vars, '', '&'); + + print_header_site_info(); + + if (!$simple) + { + echo "\n"; + + // Add in a QR code for kiosk mode + // (The QR code library requires PHP 7.4 or greater) + if (isset($context['kiosk']) && + $kiosk_QR_code && + (version_compare(PHP_VERSION, 7.4) >= 0)) + { + $url = multisite(url_base() . "/index.php?$query"); + echo '\n"; + } + } + + echo "
\n"; +} + + + +// Print a message which will only be displayed (thanks to CSS) if the user is +// using an unsupported browser. +function print_unsupported_message($context) +{ + echo "
\n"; + print_banner($context, $simple=true); + echo "
\n"; + echo "

" . get_vocab('browser_not_supported', get_vocab('mrbs_abbr')) . "

\n"; + echo "
\n"; + echo "
\n"; +} + + +// Print the page header +// $context is an associative array indexed by 'view', 'view_all', 'year', 'month', 'day', 'area' and 'room', +// any of which can be NULL. +// If $simple is true, then just print a simple header that doesn't require any database +// access or JavaScript (useful for fatal errors and database upgrades). +// When $omit_login is true the Login link is omitted. +function print_theme_header($context=null, $simple=false, $omit_login=false) +{ + global $multisite, $site, $default_view, $default_view_all, $view_week_number, $style_weekends; + + if ($simple) + { + $data = array(); + $classes = array(); + } + else + { + // Set the context values if they haven't been given + if (!isset($context)) + { + $context = array(); + } + + if (empty($context['area'])) + { + $context['area'] = get_default_area(); + } + + if (empty($context['room'])) + { + $context['room'] = get_default_room($context['area']); + } + + if (!isset($context['view'])) + { + $context['view'] = (isset($default_view)) ? $default_view : 'day'; + } + + if (!isset($context['view_all'])) + { + $context['view_all'] = (isset($default_view_all)) ? $default_view_all : true; + } + + // Need to set the timezone before we can use date() + // This will set the correct timezone for the area + get_area_settings($context['area']); + + // If we dont know the right date then use today's + if (!isset($context['year'])) + { + $context['year'] = date('Y'); + } + + if (!isset($context['month'])) + { + $context['month'] = date('m'); + } + + if (!isset($context['day'])) + { + $context['day'] = date('d'); + } + + // Get the form token now, before any headers are sent, in case we are using the 'cookie' + // session scheme. Otherwise we won't be able to store the Form token. + Form::getToken(); + + $page = this_page(false, '.php'); + + // Put some data attributes in the body element for the benefit of JavaScript. Note that we + // cannot use these PHP variables directly in the JavaScript files as those files are cached. + $data = array( + 'view' => $context['view'], + 'view_all' => $context['view_all'], + 'area' => $context['area'], + 'room' => $context['room'], + 'page' => $page, + 'page-date' => format_iso_date($context['year'], $context['month'], $context['day']), + 'is-admin' => (is_admin()) ? 'true' : 'false', + 'is-book-admin' => (is_book_admin()) ? 'true' : 'false', + 'lang-prefs' => json_encode(get_lang_preferences()) + ); + + if ($multisite && isset($site) && ($site !== '')) + { + $data['site'] = $site; + } + + if (isset($context['kiosk'])) + { + $data['kiosk'] = $context['kiosk']; + } + + $mrbs_user = session()->getCurrentUser(); + if (isset($mrbs_user)) + { + $data['username'] = $mrbs_user->username; + } + + // We need $timetohighlight for the day and week views + $timetohighlight = get_form_var('timetohighlight', 'int'); + if (isset($timetohighlight)) + { + $data['timetohighlight'] = $timetohighlight; + } + + // Put the filename in as a class to aid styling. + $classes[] = $page; + // And if the user is logged in, add another class to aid styling + if (isset($mrbs_user)) + { + $classes[] = 'logged_in'; + } + + // To help styling + if ($view_week_number) + { + $classes[] = 'view_week_number'; + } + if ($style_weekends) + { + $classes[] = 'style_weekends'; + } + + } + + $headers = array("Content-Type: text/html; charset=" . get_charset()); + http_headers($headers); + + echo DOCTYPE . "\n"; + + // We produce two tags: one for versions of IE that we don't support and one for all + // other browsers. This enables us to use CSS to hide and show the appropriate text. + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + + print_head($simple); + + echo ' $value) + { + if (isset($value)) + { + echo " data-$key=\"" . htmlspecialchars($value) . '"'; + } + } + echo ">\n"; + + print_unsupported_message($context); + + print_banner($context, $simple, $omit_login); + + // This
should really be moved out of here so that we can always see + // the matching closing
+ echo "
\n"; + + +} // end of print_theme_header() + diff --git a/mbrs/Themes/verdnatura/styling.inc b/mbrs/Themes/verdnatura/styling.inc new file mode 100644 index 0000000..b039e2b --- /dev/null +++ b/mbrs/Themes/verdnatura/styling.inc @@ -0,0 +1,127 @@ + in report.php +$report_h3_border_color = "#879AA8"; // border colour for

in report.php + +$search_table_border_color = $standard_font_color; + +$site_faq_entry_border_color = "#C3CCD3"; // used to separate individual FAQ's in help.php + +$anchor_link_color = $standard_font_color; // link color +$anchor_visited_color = $anchor_link_color; // link color (visited) +$anchor_hover_color = $anchor_link_color; // link color (hover) + +$anchor_link_color_banner = $header_font_color; // link color +$anchor_visited_color_banner = $anchor_link_color_banner; // link color (visited) +$anchor_hover_color_banner = $anchor_link_color_banner; // link color (hover) + +$anchor_link_color_header = $standard_font_color; // link color +$anchor_visited_color_header = $anchor_link_color_header; // link color (visited) +$anchor_hover_color_header = $anchor_link_color_header; // link color (hover) + +$column_hidden_color = $main_table_month_invalid_color; // hidden days in the week and month views +$calendar_hidden_color = "#dae0e4"; // hidden days in the mini-cals +$row_highlight_color = $banner_back_color; // used for highlighting a row +$row_even_color = "#ffffff"; // even rows in the day and week views +$row_odd_color = "#efefef"; // odd rows in the day and week views +$row_even_color_weekend = "#f4f4f4"; // even rows in the day and week views for weekends +$row_odd_color_weekend = "#e4e4e4"; // odd rows in the day and week views for weekends +$row_even_color_holiday = "#e8e8e8"; // even rows in the day and week views for holidays +$row_odd_color_holiday = "#d8d8d8"; // odd rows in the day and week views for holidays +$row_even_color_weekend_holiday = "#dfdfdf"; // even rows in the day and week views for weekend holidays +$row_odd_color_weekend_holiday = "#cfcfcf"; // odd rows in the day and week views for weekend holidays + +$zebra_even_color = "#ffffff"; // Colour for even rows in other tables (eg Search, Report and Users) +$zebra_odd_color = '#e2e4ff'; // Colour for odd rows in other tables (eg Search, Report and Users) + +$help_highlight_color = "#ffe6f0"; // highlighting text on the help page + +// Button colours +$button_color_stops = array('#eeeeee', '#cccccc'); // linear gradient colour stops +$button_inset_color = 'darkblue'; + +// These are the colours used for distinguishing between the different types of bookings in the main +// displays in the day, week and month views +$color_types = array( + 'A' => "#ffff99", + 'B' => "#99cccc", + 'C' => "#ffffcd", + 'D' => "#cde6e6", + 'E' => "#6dd9c4", + 'F' => "#82adad", + 'G' => "#ccffcc", + 'H' => "#d9d982", + 'I' => "#99cc66", + 'J' => "#e6ffe6"); + +// colours used for pending.php and bookings awaiting approval +$outstanding_color = "#FFF36C"; // font colour for the outstanding reservations message in the header +$pending_header_back_color = $header_back_color; // background colour for series headers +$series_entry_back_color = "#FFFCDA"; // background colour for entries in a series +$pending_control_color = "#FFF36C"; // background colour for the series +/- controls in pending.php +$attention_color = 'darkorange'; // background colour for the number of bookings awaiting approval + +// ***** DIMENSIONS ******************* +$banner_border_width = '0'; // (px) border width for the outside of the banner +$banner_border_cell_width = '1'; // (px) border width for the cells of the banner +$main_table_border_width = '0'; // (px) border width for the outside of the main day/week/month tables +$main_table_cell_border_width = '1'; // (px) vertical border width for the cells of the main day/week/month tables +$main_cell_height = '1.5em'; // height of the cells in the main day/week tables + + +// ***** FONTS ************************ +$standard_font_family = "Arial, 'Arial Unicode MS', Verdana, sans-serif"; + diff --git a/mbrs/kubernetes/mbrs.yml b/mbrs/kubernetes/mbrs.yml new file mode 100644 index 0000000..591bd96 --- /dev/null +++ b/mbrs/kubernetes/mbrs.yml @@ -0,0 +1,163 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: mrbs + labels: + app: mrbs +spec: + ingressClassName: nginx + rules: + - host: mrbs.verdnatura.es + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: mrbs + port: + number: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: mrbs + labels: + app: mrbs +spec: + ports: + - port: 80 + targetPort: 80 + selector: + app: mrbs +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mrbs + labels: + app: mrbs +spec: + replicas: 1 + selector: + matchLabels: + app: mrbs + strategy: + type: Recreate + template: + metadata: + labels: + app: mrbs + spec: + containers: + - image: registry.verdnatura.es/mbrs:1.11.1-vn1 + ## name: main ??? + ports: + - containerPort: 80 + resources: + limits: + memory: 1Gi + volumeMounts: + - name: secret + mountPath: /var/www/html/web/config.inc.php + subPath: config.inc.php + restartPolicy: Always + volumes: + - name: secret + secret: + secretName: mbrs.config + defaultMode: 420 +--- +apiVersion: v1 +kind: Service +metadata: + name: mrbs-db + labels: + app: mrbs-db +spec: + type: NodePort + ports: + - port: 3306 + targetPort: 3306 + selector: + io.kompose.service: mrbs-db +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mrbs.db + labels: + app: mrbs-db +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: mrbs-db + strategy: + type: Recreate + template: + metadata: + labels: + app: mrbs + io.kompose.service: mrbs-db + spec: + containers: + - image: mysql:8.0.33 + name: mrbs-db + args: + - --transaction-isolation=READ-COMMITTED + - --binlog-format=ROW + - --ignore-db-dir=lost+found + env: + - name: MYSQL_DATABASE + value: mrbs + - name: MYSQL_USER + value: mrbs + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: mrbs.env + key: MYSQL_PASSWORD + ports: + - containerPort: 3306 + resources: + limits: + memory: 500Mi + volumeMounts: + - mountPath: /var/lib/mysql + name: mrbs-db + restartPolicy: Always + volumes: + - name: db + persistentVolumeClaim: + claimName: mrbs.db +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mrbs.db + labels: + app: mrdb-db +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 4Gi + storageClassName: csi-rbd-ssd-sc + volumeMode: Filesystem +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mrbs.web + labels: + app: mrbs +spec: + accessModes: + - ReadWriteOnce + volumeMode: Filesystem + resources: + requests: + storage: 4Gi + storageClassName: csi-rbd-ssd-sc diff --git a/mbrs/kubernetes/secrets.yml b/mbrs/kubernetes/secrets.yml new file mode 100644 index 0000000..0e542df --- /dev/null +++ b/mbrs/kubernetes/secrets.yml @@ -0,0 +1,58 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mbrs.env + labels: + app: mbrs +type: Opaque +stringData: + MYSQL_PASSWORD: dhFtyr6%3jfifjRL.F987? + MYSQL_ROOT_PASSWORD: 3TTEKrPXspMAvHp +--- +apiVersion: v1 +kind: Secret +metadata: + name: mbrs.config + labels: + app: mbrs +type: Opaque +stringData: + config.inc.php: | +