33 |
34 |
35 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:8.2-apache
2 |
3 | ENV APP_DIR /var/munkireport
4 |
5 | RUN apt-get update && \
6 | apt-get install --no-install-recommends -y libldap2-dev \
7 | libcurl4-openssl-dev \
8 | libzip-dev \
9 | unzip \
10 | zlib1g-dev \
11 | libxml2-dev && \
12 | apt-get clean && \
13 | rm -rf /var/lib/apt/lists/*
14 |
15 | RUN docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ && \
16 | docker-php-ext-install -j$(nproc) curl pdo_mysql soap ldap zip
17 |
18 | ENV COMPOSER_ALLOW_SUPERUSER 1
19 | ENV COMPOSER_HOME /tmp
20 | ENV SITENAME MunkiReport
21 | ENV MODULES ard, bluetooth, disk_report, munkireport, managedinstalls, munkiinfo, network, security, warranty
22 | ENV INDEX_PAGE ""
23 | ENV AUTH_METHODS NOAUTH
24 |
25 | COPY . $APP_DIR
26 |
27 | WORKDIR $APP_DIR
28 |
29 | COPY --from=composer:2.2.6 /usr/bin/composer /usr/local/bin/composer
30 |
31 | RUN composer install --no-dev && \
32 | composer dumpautoload -o
33 |
34 | RUN mkdir -p app/db && \
35 | chmod -R 777 app/db
36 |
37 | RUN php please migrate
38 |
39 | RUN rm -rf /var/www/html && \
40 | ln -s /var/munkireport/public /var/www/html
41 |
42 | RUN sed -i 's/ServerTokens OS/ServerTokens Prod/' /etc/apache2/conf-available/security.conf
43 |
44 | RUN sed -i 's/ServerSignature On/ServerSignature Off/' /etc/apache2/conf-available/security.conf
45 |
46 | RUN a2enmod rewrite
47 |
48 | EXPOSE 80
49 |
--------------------------------------------------------------------------------
/public/assets/css/dataTables-bootstrap.css:
--------------------------------------------------------------------------------
1 |
2 | div.dataTables_length label {
3 | float: left;
4 | text-align: left;
5 | }
6 |
7 | div.dataTables_length select {
8 | width: 75px;
9 | }
10 |
11 | div.dataTables_filter label {
12 | float: right;
13 | }
14 |
15 | div.dataTables_info {
16 | padding-top: 8px;
17 | }
18 |
19 | div.dataTables_paginate {
20 | float: right;
21 | margin: 0;
22 | }
23 |
24 | table.table {
25 | clear: both;
26 | margin-bottom: 6px !important;
27 | }
28 |
29 | table.table thead .sorting,
30 | table.table thead .sorting_asc,
31 | table.table thead .sorting_desc,
32 | table.table thead .sorting_asc_disabled,
33 | table.table thead .sorting_desc_disabled {
34 | cursor: pointer;
35 | *cursor: hand;
36 | }
37 |
38 | table.table thead .sorting { background: none center right; }
39 | table.table thead .sorting { background: url('../images/sort_both.png') no-repeat center right; }
40 | table.table thead .sorting_asc { background: url('../images/sort_asc.png') no-repeat center right; }
41 | table.table thead .sorting_desc { background: url('../images/sort_desc.png') no-repeat center right; }
42 |
43 | table.table thead .sorting_asc_disabled { background: url('../images/sort_asc_disabled.png') no-repeat center right; }
44 | table.table thead .sorting_desc_disabled { background: url('../images/sort_desc_disabled.png') no-repeat center right; }
45 | table.dataTable th:active {
46 | outline: none;
47 | }
--------------------------------------------------------------------------------
/public/assets/client_installer/payload/usr/local/munki/postflight:
--------------------------------------------------------------------------------
1 | #!/usr/local/munki/munki-python
2 |
3 | import os
4 | import subprocess
5 | import sys
6 |
7 | TOUCH_FILE_PATH = '/Users/Shared/.com.github.munkireport.run'
8 | LAUNCHD = 'com.github.munkireport.runner'
9 | LAUNCHD_PATH = '/Library/LaunchDaemons/{}.plist'.format(LAUNCHD)
10 | SUBMIT_SCRIPT = '/usr/local/munkireport/munkireport-runner'
11 |
12 | def write_touch_file():
13 | if os.path.exists(TOUCH_FILE_PATH):
14 | os.remove(TOUCH_FILE_PATH)
15 |
16 | if not os.path.exists(TOUCH_FILE_PATH):
17 | with open(TOUCH_FILE_PATH, 'a'):
18 | os.utime(TOUCH_FILE_PATH, None)
19 |
20 | def ensure_launchd_loaded():
21 | cmd =[
22 | '/bin/launchctl',
23 | 'list'
24 | ]
25 | loaded_launchds = subprocess.check_output(cmd).decode('utf-8', 'ignore')
26 | # load the launchd if it's not loaded and is present on disk
27 | if LAUNCHD not in loaded_launchds and os.path.exists(LAUNCHD_PATH):
28 | cmd = [
29 | '/bin/launchctl',
30 | 'load',
31 | LAUNCHD_PATH
32 | ]
33 | subprocess.check_call(cmd)
34 |
35 | def main():
36 | write_touch_file()
37 | ensure_launchd_loaded()
38 | # If the launchd isn't present, call the submit script old school
39 | if not os.path.exists(LAUNCHD_PATH):
40 | subprocess.check_call(SUBMIT_SCRIPT)
41 |
42 | if __name__ == '__main__':
43 | main()
44 |
--------------------------------------------------------------------------------
/app/config/db.php:
--------------------------------------------------------------------------------
1 | 'sqlite',
8 | 'database' => env('CONNECTION_DATABASE', APP_ROOT . 'app/db/db.sqlite'),
9 | 'username' => '',
10 | 'password' => '',
11 | 'options' => env('CONNECTION_OPTIONS', []),
12 | ];
13 | break;
14 | case 'mysql':
15 | return [
16 | 'driver' => 'mysql',
17 | 'host' => env('CONNECTION_HOST', '127.0.0.1'),
18 | 'port' => env('CONNECTION_PORT', 3306),
19 | 'database' => env('CONNECTION_DATABASE', 'munkireport'),
20 | 'username' => env('CONNECTION_USERNAME', 'munkireport'),
21 | 'password' => env('CONNECTION_PASSWORD', 'munkireport'),
22 | 'charset' => env('CONNECTION_CHARSET', 'utf8mb4'),
23 | 'collation' => env('CONNECTION_COLLATION', 'utf8mb4_unicode_ci'),
24 | 'strict' => env('CONNECTION_STRICT', true),
25 | 'engine' => env('CONNECTION_ENGINE', 'InnoDB'),
26 | 'ssl_enabled' => env('CONNECTION_SSL_ENABLED', false),
27 | 'ssl_key' => env('CONNECTION_SSL_KEY'),
28 | 'ssl_cert' => env('CONNECTION_SSL_CERT'),
29 | 'ssl_ca' => env('CONNECTION_SSL_CA'),
30 | 'ssl_capath' => env('CONNECTION_SSL_CAPATH'),
31 | 'ssl_cipher' => env('CONNECTION_SSL_CIPHER'),
32 | 'options' => env('CONNECTION_OPTIONS', []),
33 | ];
34 | default:
35 | throw new \Exception(sprintf("Unknown driver: %s", $driver), 1);
36 | break;
37 | }
38 |
--------------------------------------------------------------------------------
/docker-compose.yml.example:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | munkireport:
4 | image: ghcr.io/munkireport/munkireport-php:5.x
5 | restart: always
6 | environment:
7 | - MODULES=applications, directory_service, disk_report, displays_info, extensions, filevault_status, homebrew, homebrew_info, ibridge, installhistory, inventory, localadmin, managedinstalls, mdm_status, munkiinfo, munkireport, munkireportinfo, network, power, printer, profile, security, softwareupdate, sophos, supported_os, timemachine, usage_stats, user_sessions, warranty, wifi
8 | - SITENAME=Munkireport
9 | - CONNECTION_DRIVER=mysql
10 | - CONNECTION_HOST=db
11 | - CONNECTION_PORT=3306
12 | - CONNECTION_DATABASE=munkireport
13 | - CONNECTION_USERNAME=munkireport
14 | - CONNECTION_PASSWORD=
15 | - AUTH_METHODS=LOCAL
16 | - PUID=1000
17 | - PGID=1000
18 | - CLIENT_PASSPHRASES=
19 | - WEBHOST=https://munkireport.domain.com
20 | - TZ=Europe/Berlin
21 | depends_on:
22 | - db
23 | ports:
24 | - 80:80
25 | volumes:
26 | - ./munkireport-db/:/var/munkireport/app/db
27 | - ./user//:/var/munkireport/local/users
28 | db:
29 | image: mariadb:latest
30 | restart: always
31 | environment:
32 | - MYSQL_ROOT_PASSWORD=
33 | - MYSQL_DATABASE=munkireport
34 | - MYSQL_USER=munkireport
35 | - MYSQL_PASSWORD=
36 | - PUID=1000
37 | - PGID=1000
38 | - TZ=Europe/Berlin
39 | volumes:
40 | - ./db/:/var/lib/mysql
41 |
--------------------------------------------------------------------------------
/public/assets/js/d3/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010-2015, Michael Bostock
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * The name Michael Bostock may not be used to endorse or promote products
15 | derived from this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/public/assets/css/admin.css:
--------------------------------------------------------------------------------
1 | .admin_grid{
2 | margin-top: 1em;
3 | width: 400px;
4 | }
5 | .admin_grid img{
6 | border: 0;
7 | vertical-align: middle;
8 | }
9 |
10 | a.add_link, a.edit_link{
11 | background-repeat: no-repeat;
12 | padding-left: 20px;
13 | }
14 | a.add_link{
15 | background-image: url('/images/add.png');
16 | }
17 | a.edit_link{
18 | background-image: url('/images/pencil.png');
19 | }
20 |
21 | /* admin list view */
22 | .crud_table .grid {
23 | border-collapse: collapse;
24 | border: 1px solid #ccc;
25 | }
26 | .crud_table .grid .odd{
27 | background-color: #eee;
28 | }
29 | .crud_table .grid th{
30 | background: url("/images/menubg.png") no-repeat scroll -20px top transparent;
31 | color: #fff;
32 | font-weight: normal;
33 | text-transform: capitalize;
34 | }
35 | .crud_table .grid td a{
36 | color: #286571;
37 | }
38 | .crud_table .grid td.col_0 div{
39 | float: left;
40 | }
41 | .crud_table .grid td.col_0 .edit_link, .crud_table .grid td.col_0 .delete-button{
42 | display:block;
43 | height:17px;
44 | width:20px;
45 | padding:0;
46 | text-indent:-400px;
47 | background-repeat: no-repeat;
48 | }
49 | .crud_table .grid td.col_0 .delete-button{
50 | font-size: inherit;
51 | background-image: url('/images/delete.png');
52 | cursor: pointer;
53 | }
54 |
55 | /* admin add/edit view */
56 | .crud_edit label, .crud_add label{
57 | font-size: 12px;
58 | }
59 | .crud_edit input, .crud_add input,
60 | .crud_edit select, .crud_add select{
61 | width: 15em;
62 | }
--------------------------------------------------------------------------------
/docs/localize.md:
--------------------------------------------------------------------------------
1 | Localizing
2 | ==========
3 |
4 | If you are developing for MunkiReport, please make sure the strings you use are provided via the i18n localization framework.
5 |
6 | Files
7 | -----
8 |
9 | The localization files are located in `assets/locales/`. These are JSON files. The JSON format has some restrictions in the use of quotations etc. so please make sure the locale files are valid JSON, you can check your file using a validator like [jsonlint.com](http://jsonlint.com).
10 |
11 | dataTables
12 | ----------
13 |
14 | When you're adding an additional language, make sure you add the appropriate localization file for `assets/locales/dataTables` as well, the tables won't load if it can't find the locale file. You can find dataTables locale files in the [dataTables github repo](https://github.com/DataTables/Plugins/tree/master/i18n).
15 | Make sure you:
16 |
17 | * remove the comments at the beginning of the file
18 | * remove the sProcessing property (MunkiReport shows a spinner instead)
19 | * remove the colon (:) from sSearch (MunkiReport moves this into the placeholder)
20 |
21 | Some things to keep in mind
22 | ---------------------------
23 |
24 | * Place generic words like 'computer', 'memory', 'hour' in the root of the JSON object
25 | * Try to find an appropriate place for other words and sentences (look at what's already localized)
26 | * English is the fallback language, so make sure the strings are at least available in en.json
27 | * Try to keep the JSON files alphabetically organized, this will make it a lot easier for people maintaining localization files.
--------------------------------------------------------------------------------
/app/views/error/client_error.php:
--------------------------------------------------------------------------------
1 |
2 | view('partials/head'); ?>
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
Error
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | You are not allowed to view this page
27 |
28 |
29 |
30 | Page not found
31 |
32 |
33 |
34 | You are required to visit this site using a secure connection.
35 | Go to secure site
36 |
37 |
38 |
39 |
40 | MunkiReport is down for maintenance.
41 |
42 |
43 |
44 | Unknown error
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | view('partials/foot'); ?>
67 |
--------------------------------------------------------------------------------
/public/assets/client_installer/payload/usr/local/munkireport/munkilib/constants.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | #
3 | # Copyright 2009-2023 Greg Neagle.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the 'License');
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an 'AS IS' BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | """constants.py.
17 |
18 | Created by Greg Neagle on 2016-12-14.
19 |
20 | Commonly used constants
21 | """
22 |
23 | # NOTE: it's very important that defined exit codes are never changed!
24 | # Preflight exit codes.
25 | EXIT_STATUS_PREFLIGHT_FAILURE = 1 # Python crash yields 1.
26 | # Client config exit codes.
27 | EXIT_STATUS_OBJC_MISSING = 100
28 | EXIT_STATUS_MUNKI_DIRS_FAILURE = 101
29 | # Server connection exit codes.
30 | EXIT_STATUS_SERVER_UNAVAILABLE = 150
31 | # User related exit codes.
32 | EXIT_STATUS_INVALID_PARAMETERS = 200
33 | EXIT_STATUS_ROOT_REQUIRED = 201
34 |
35 | BUNDLE_ID = "MunkiReport"
36 | MANAGED_INSTALLS_PLIST_PATH = "/Library/Preferences/" + BUNDLE_ID + ".plist"
37 | SECURE_MANAGED_INSTALLS_PLIST_PATH = (
38 | "/private/var/root/Library/Preferences/" + BUNDLE_ID + ".plist"
39 | )
40 |
41 | ADDITIONAL_HTTP_HEADERS_KEY = "AdditionalHttpHeaders"
42 |
43 | if __name__ == "__main__":
44 | print("This is a library of support tools for the Munki Suite.")
45 |
--------------------------------------------------------------------------------
/app/controllers/Archiver.php:
--------------------------------------------------------------------------------
1 | authorized() || jsonError('Authenticate first', 403);
13 | $this->authorized('archive') || jsonError('You need to be archiver, manager or admin', 403);
14 |
15 | // Connect to database
16 | $this->connectDB();
17 | }
18 |
19 |
20 | //===============================================================
21 |
22 | public function index()
23 | {
24 | echo 'Archiver';
25 | }
26 |
27 | //===============================================================
28 |
29 | public function update_status($serial_number = '')
30 | {
31 | if (! isset($_POST['status'])) {
32 | jsonError('No status found');
33 | }
34 | $changes = Reportdata_model::where('serial_number', $serial_number)
35 | ->update(
36 | [
37 | 'archive_status' => intval($_POST['status']),
38 | ]
39 | );
40 | jsonView(['updated' => intval($_POST['status'])]);
41 | }
42 |
43 | public function bulk_update_status()
44 | {
45 | if( ! $days = intval(post('days'))){
46 | jsonError('No days sent');
47 | }
48 | $expire_timestamp = time() - ($days * 24 * 60 * 60);
49 | $changes = Reportdata_model::where('timestamp', '<', $expire_timestamp)
50 | ->where('archive_status', 0)
51 | ->update(['archive_status' => 1]);
52 | jsonView(['updated' => $changes]);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/lib/munkireport/Recaptcha.php:
--------------------------------------------------------------------------------
1 | secret = $secret;
22 | }
23 |
24 | public function verify($recaptcharesponse, $userip)
25 | {
26 |
27 | //verifying recaptcha with google
28 | try {
29 | $data = array(
30 | 'secret' => $this->secret,
31 | 'response' => $recaptcharesponse,
32 | 'remoteip' => $userip,
33 | );
34 | $options = array(
35 | 'http' => array(
36 | 'header' => "Content-type: application/x-www-form-urlencoded\r\n",
37 | 'method' => 'POST',
38 | 'content' => http_build_query($data),
39 | )
40 | );
41 | $options = array(
42 | 'form_params' => [
43 | 'secret' => $this->secret,
44 | 'response' => $recaptcharesponse,
45 | 'remoteip' => $userip,
46 | ],
47 | );
48 | $client = new Request();
49 | $result = $client->post($this->url, $options);
50 | $this->json_result = json_decode($result);
51 |
52 | if ($this->json_result->success == 1) {
53 | return true;
54 | }
55 | } catch (Exception $e) {
56 | error($e->getMessage(), '');
57 | }
58 |
59 | return false;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/lib/munkireport/AuthWhitelist.php:
--------------------------------------------------------------------------------
1 | config = $config;
11 | }
12 |
13 | private function parse_range($ip, $range) {
14 | if (strpos($range, '/')) return $this->cidr_match($ip, $range);
15 | $range = $range . '/32';
16 | return $this->cidr_match($ip, $range);
17 | }
18 |
19 | private function cidr_match($ip, $range) {
20 | list ($subnet, $bits) = explode('/', $range);
21 | $ip = ip2long($ip);
22 | $subnet = ip2long($subnet);
23 | $mask = -1 << (32 - $bits);
24 | $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned
25 | return ($ip & $mask) == $subnet;
26 | }
27 |
28 | public function check_ip($remote_address) {
29 | // if user is going to the report uri, allow the connection regardless
30 | if (substr(($GLOBALS[ 'engine' ]->get_uri_string()), 0, 8) === "report/") { return 1; }
31 |
32 | // for loop through the configuration setting to check if any IP addresses match - if so
33 | // allow traffic
34 |
35 | foreach ($this->config['whitelist_ipv4'] as $range) {
36 | if ($this->parse_range($remote_address, $range)) { return 1; }
37 | }
38 |
39 | // if a custom 403 page is defined, send traffic to that page
40 | if (isset($this->config['redirect_unauthorized']) && ! empty($this->config['redirect_unauthorized'])) {
41 | header(("Location: " . ($this->config['redirect_unauthorized'])), true, 301);
42 | exit();
43 | }
44 |
45 | // otherwise send it to the local servers 403 page
46 | redirect('error/client_error/403');
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/lib/munkireport/Listing.php:
--------------------------------------------------------------------------------
1 | listingData = $listingData;
15 | $this->template = 'listings/default';
16 | return $this;
17 | }
18 |
19 | public function render($data = [])
20 | {
21 | if( ! $this->listingData){
22 | $this->_renderPageNotFound();
23 | }
24 |
25 | $data = [
26 | 'page' => 'clients',
27 | 'scripts' => ["clients/client_list.js"],
28 | ] + $data;
29 |
30 | if( $this->_getType($this->listingData) == 'yaml'){
31 | $this->_renderYAML($this->listingData, $data);
32 | }else{
33 | $this->_renderPHP($this->listingData, $data);
34 | }
35 | }
36 |
37 | private function _renderPHP($listingData, $data)
38 | {
39 | view($listingData->view, $data, $listingData->view_path);
40 | }
41 |
42 | private function _renderYAML($listingData, $data)
43 | {
44 | $data = $data + Yaml::parseFile($this->_getPath($listingData, 'yml'));
45 | view($this->template, $data);
46 | }
47 |
48 | private function _renderPageNotFound()
49 | {
50 | $data = ['status_code' => 404];
51 | $view = 'error/client_error';
52 | view($view, $data);
53 | exit;
54 | }
55 |
56 | private function _getType($pathComponents)
57 | {
58 | return is_readable( $this->_getPath($pathComponents, 'yml')) ? 'yaml' : 'php';
59 | }
60 |
61 | private function _getPath($pathComponents, $extension)
62 | {
63 | return $pathComponents->view_path . $pathComponents->view . '.' . $extension;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/database/migrations/2017_02_20_085316_machine_group.php:
--------------------------------------------------------------------------------
1 | hasTable($this->tableNameV2)) {
17 | // Migration already failed before, but didnt finish
18 | throw new Exception("previous failed migration exists");
19 | }
20 |
21 | if ($capsule::schema()->hasTable($this->tableName)) {
22 | $capsule::schema()->rename($this->tableName, $this->tableNameV2);
23 | $migrateData = true;
24 | }
25 |
26 | $capsule::schema()->create($this->tableName, function (Blueprint $table) {
27 | $table->increments('id');
28 | $table->integer('groupid')->nullable();
29 | $table->string('property');
30 | $table->string('value');
31 | });
32 |
33 | if ($migrateData) {
34 | $capsule::unprepared("INSERT INTO
35 | $this->tableName
36 | SELECT
37 | id,
38 | groupid,
39 | property,
40 | value
41 | FROM
42 | $this->tableNameV2");
43 | }
44 | }
45 |
46 | public function down()
47 | {
48 | $capsule = new Capsule();
49 | $capsule::schema()->dropIfExists($this->tableName);
50 | if ($capsule::schema()->hasTable($this->tableNameV2)) {
51 | $capsule::schema()->rename($this->tableNameV2, $this->tableName);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/database/migrations/0213_00_00_000001_cleanup_hash_table.php:
--------------------------------------------------------------------------------
1 | getLegacyModelSchemaVersion('hash');
19 | $capsule = new Capsule();
20 |
21 | if ($legacyVersion !== null && $legacyVersion < static::$legacySchemaVersion) {
22 | $rename_list = array(
23 | 'InstallHistory' => 'installhistory',
24 | 'Machine' => 'machine',
25 | 'InventoryItem' => 'inventory',
26 | 'inventoryitem' => 'inventory',
27 | 'Munkireport' => 'munkireport',
28 | 'Reportdata' => 'reportdata',
29 | 'filevault_status_model' => 'filevault_status',
30 | 'localadmin_model' => 'localadmin',
31 | 'network_model' => 'network',
32 | 'disk_report_model' => 'disk_report'
33 | );
34 |
35 | foreach ($rename_list as $from => $to) {
36 | $capsule::table('hash')
37 | ->where('name', '=', $from)
38 | ->update(Array('name' => $to));
39 | }
40 |
41 | $this->markLegacyMigrationRan();
42 | }
43 | }
44 |
45 | // public function down() {
46 | // $legacyVersion = $this->getLegacyModelSchemaVersion('hash');
47 | //
48 | // if ($legacyVersion == static::$legacySchemaVersion) {
49 | //
50 | //
51 | // $this->markLegacyRollbackRan();
52 | // }
53 | // }
54 | }
--------------------------------------------------------------------------------
/app/lib/munkireport/AuthLDAP.php:
--------------------------------------------------------------------------------
1 | config = $config;
12 | $this->groups = [];
13 | }
14 |
15 | public function login($login, $password)
16 | {
17 | $this->login = $login;
18 |
19 | if ($login && $password) {
20 | include_once(APP_PATH . '/lib/authLDAP/authLDAP.php');
21 | $ldap_auth_obj = new \Auth_ldap($auth_data);
22 | if ($ldap_auth_obj->authenticate($login, $password)) {
23 |
24 | // Get groups
25 | if ($user_data = $ldap_auth_obj->getUserData($login)) {
26 | foreach($user_data['grps'] as $group){
27 | $this->groups[] = $group;
28 | }
29 | }
30 |
31 | $auth_data = [
32 | 'user' => $login,
33 | 'groups' => $this->groups,
34 | ];
35 |
36 | if ($this->authorizeUserAndGroups($this->config, $auth_data)){
37 | $this->authStatus = 'success';
38 | return true;
39 | }
40 |
41 | $this->authStatus = 'unauthorized';
42 | return false;
43 | }
44 | }
45 |
46 | return false;
47 | }
48 |
49 | public function getAuthMechanism()
50 | {
51 | return 'ldap';
52 | }
53 |
54 | public function getAuthStatus()
55 | {
56 | return $this->authStatus;
57 | }
58 |
59 | public function getUser()
60 | {
61 | return $this->login;
62 | }
63 |
64 | public function getGroups()
65 | {
66 | return $this->groups;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/database/builders/MRQueryBuilder.php:
--------------------------------------------------------------------------------
1 | where($this->from.'.serial_number', $serial_number);
12 | }
13 |
14 | public function filter($what = '')
15 | {
16 | $this->filterMachineGroup();
17 | if($what != 'groupOnly'){
18 | $this->filterArchived();
19 | }
20 | return $this;
21 | }
22 |
23 | private function filterMachineGroup()
24 | {
25 | $key = 'serial_number';
26 | $table = 'reportdata';
27 | if($this->from != $table){
28 | $this->join(
29 | $table,
30 | $table.'.'.$key,
31 | '=',
32 | $this->from.'.'.$key
33 | );
34 | }
35 | if ($groups = get_filtered_groups()) {
36 | $this->whereIn('machine_group', $groups);
37 | }
38 | }
39 |
40 | private function filterArchived()
41 | {
42 | if( is_archived_filter_on()) {
43 | $this->where('reportdata.archive_status', 0);
44 | }elseif( is_archived_only_filter_on() ){
45 | $this->where('reportdata.archive_status', '!=', 0);
46 | }
47 | }
48 |
49 | public function insertChunked(array $values, int $chunkSize = 0)
50 | {
51 | if (empty($values)) {
52 | return true;
53 | }
54 |
55 | if (! is_array(reset($values))) {
56 | $values = [$values];
57 | }
58 |
59 | if ( $chunkSize === 0 ){
60 | // Calculate chunksize based on SQLite limits (max 999 inserts)
61 | $itemCount = count($values[0]);
62 | $chunkSize = floor(999 / $itemCount);
63 | }
64 |
65 | // Insert chunked data
66 | foreach( array_chunk ($values , $chunkSize, TRUE ) as $chunk ){
67 | $this->insert($chunk);
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/lib/munkireport/Email.php:
--------------------------------------------------------------------------------
1 | config = $config;
13 | }
14 |
15 | /**
16 | * Send email
17 | *
18 | * Send email
19 | *
20 | * @param array $email Email properties
21 | **/
22 | public function send($email)
23 | {
24 | $out = array('error' => 0, 'error_msg' => '');
25 |
26 | include_once(APP_PATH . '/lib/phpmailer/class.phpmailer.php');
27 | include_once(APP_PATH . '/lib/phpmailer/class.smtp.php');
28 | $mail = new \PHPMailer;
29 |
30 | // Get from
31 | list($from_addr, $from_name) = each($this->config['from']);
32 |
33 | $mail->isSMTP(); // Set mailer to use SMTP
34 | $mail->Host = $this->config['smtp_host']; // Specify main and backup SMTP servers
35 | $mail->SMTPAuth = $this->config['smtp_auth']; // Enable SMTP authentication
36 | $mail->Username = $this->config['smtp_username']; // SMTP username
37 | $mail->Password = $this->config['smtp_password']; // SMTP password
38 | $mail->SMTPSecure = $this->config['smtp_secure']; // Enable TLS encryption, `ssl` also accepted
39 | $mail->Port = $this->config['smtp_port']; // TCP port to connect to
40 | $mail->CharSet = "UTF-8";
41 | $mail->setFrom($from_addr, $from_name);
42 |
43 | // Add recipient(s)
44 | foreach ($email['to'] as $to_addr => $to_name) {
45 | $mail->addAddress($to_addr, $to_name);
46 | }
47 |
48 | $mail->isHTML(true); // Set email format to HTML
49 | $mail->Subject = $email['subject'];
50 | $mail->Body = $email['content'];
51 |
52 | if (! $mail->send()) {
53 | $out['error'] = 1;
54 | $out['error_msg'] = $mail->ErrorInfo;
55 | }
56 |
57 | return $out;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/views/install/install_plist.php:
--------------------------------------------------------------------------------
1 | ' . "\n";
7 | ?>
8 |
9 |
10 |
11 | autoremove
12 |
13 | catalogs
14 |
15 | testing
16 |
17 | name
18 | munkireport
19 | display_name
20 | Munkireport Install and config
21 | installer_type
22 | nopkg
23 | installs
24 |
25 |
26 | path
27 | /usr/local/munki/munkireport-
28 | type
29 | file
30 |
31 |
32 | preinstall_script
33 | #!/bin/bash
34 | /bin/bash -c "$(curl -s --max-time 10 index.php?/install)"
38 | minimum_os_version
39 | 10.9.0
40 | unattended_install
41 |
42 | uninstallable
43 |
44 | uninstall_method
45 | uninstall_script
46 | uninstall_script
47 | #!/bin/sh
48 | rm -rf /usr/local/munki/postflight \
49 | /usr/local/munki/report_broken_client \
50 | /usr/local/munki/munkilib/reportcommon.py* \
51 | /usr/local/munki/munkireport-* \
52 | /usr/local/munkireport/ \
53 | /Library/MunkiReport/ \
54 | /Library/LaunchDaemons/com.github.munkireport.runner.plist \
55 | /Library/Preferences/MunkiReport.plist
56 | exit 0
57 | version
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/docs/autopkg.md:
--------------------------------------------------------------------------------
1 | AutoPkg for munkireport
2 | =====
3 |
4 | If your munkireport server is running fine and you can create an install package from it, you can automate the process of creating installation packages using the munkireport autopkg recipe. This will help you create a new package suited for your organization when you update your munkireport server.
5 |
6 | Thi guide assumes you have installed [AutoPkg](https://github.com/autopkg/autopkg) and you have some experience working with AutoPkg.
7 |
8 | First add the munkireport recipe repository:
9 |
10 | ```sh
11 | autopkg repo-add munkireport-recipes
12 | ```
13 |
14 | This will give you two recipes, munkireport.pkg.recipe and munkireport.munki.recipe. The first will create a package, the second will add it to your munki repository. You cannot use the recipes at the moment, because you have to add some information about your munkireport installation. To add that, you'll need to make an override:
15 |
16 | ```sh
17 | autopkg make-override munkireport.munki
18 | ```
19 |
20 | AutoPkg has created an override file for you: ~/Library/AutoPkg/RecipeOverrides/munkireport.munki.recipe. Now you can edit the file with your favorite editor.
21 |
22 | The most important setting you have to change is
23 |
24 | ```xml
25 | BASEURL
26 | http://localhost:8888
27 | ```
28 |
29 | This has to be the url that you use to access your munkireport server minus /index.php?etc.
30 |
31 | You could also change MUNKI_REPO_SUBDIR, NAME and modules. If you add modules here, you will force the modules that get packaged into the munkireport package, the modules you set in config.php will be ignored. Add modules in the following way:
32 |
33 | ```xml
34 | modules
35 |
36 | localadmin
37 | bluetooth
38 |
39 | ```
40 |
41 |
42 | You can test your new recipe by running
43 |
44 | ```sh
45 | autopkg run -v munkireport.munki.recipe
46 | ```
47 |
48 | If all goes well, you'll have imported a new Munkireport package into your munki repository.
49 |
--------------------------------------------------------------------------------
/app/lib/munkireport/I18next.php:
--------------------------------------------------------------------------------
1 | locale = $locale ? $locale : 'en';
22 |
23 | // Load the localisation JSON
24 | if ($json = @file_get_contents(PUBLIC_ROOT . 'assets/locales/' . $this->locale . '.json')) {
25 | $this->i18nArray = json_decode($json, true);
26 | } else {
27 | $this->i18nArray = array();
28 | }
29 | }
30 |
31 | /**
32 | * Translate
33 | * *
34 | * @param type var Description
35 | **/
36 | public function translate($text = '', $params = '')
37 | {
38 | $textArray = explode('.', $text);
39 | $search = $this->i18nArray;
40 | foreach ($textArray as $part) {
41 | if (! is_array($search) or ! isset($search[$part])) {
42 | return $text;
43 | }
44 | $parent = $search;
45 | $search = $parent[$part];
46 | }
47 |
48 | if (is_array($search)) {
49 | return 'Array found for ' . $text;
50 | }
51 |
52 | // Check if there are params
53 | if ($params) {
54 | $paramsArray = json_decode($params, true);
55 |
56 | // Check if there is a count param
57 | if (isset($paramsArray['count']) && ($paramsArray['count'] == 0 || $paramsArray['count'] > 1 )) {
58 | if (isset($parent[$part.'_plural'])) {
59 | $search = $parent[$part.'_plural'];
60 | }
61 | }
62 |
63 | // Replace params
64 | foreach ($paramsArray as $find => $replace) {
65 | $search = str_replace('__'. $find . '__', $replace, $search);
66 | }
67 | }
68 |
69 | return $search;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/database/migrations/2017_12_18_173230_business_unit.php:
--------------------------------------------------------------------------------
1 | hasTable($this->tableNameV2)) {
17 | // Migration already failed before, but didnt finish
18 | throw new Exception("previous failed migration exists");
19 | }
20 |
21 | if ($capsule::schema()->hasTable($this->tableName)) {
22 | $capsule::schema()->rename($this->tableName, $this->tableNameV2);
23 | $migrateData = true;
24 | }
25 |
26 | $capsule::schema()->create($this->tableName, function (Blueprint $table) {
27 | $table->increments('id');
28 | $table->integer('unitid');
29 | $table->string('property');
30 | $table->string('value');
31 | });
32 |
33 | if ($migrateData) {
34 | $capsule::unprepared("INSERT INTO
35 | $this->tableName
36 | SELECT
37 | id,
38 | unitid,
39 | property,
40 | value
41 | FROM
42 | $this->tableNameV2");
43 | $capsule::schema()->drop($this->tableNameV2);
44 | }
45 |
46 | // (Re)create indexes
47 | $capsule::schema()->table($this->tableName, function (Blueprint $table) {
48 | $table->index('property');
49 | $table->index('value');
50 | });
51 | }
52 |
53 | public function down()
54 | {
55 | $capsule = new Capsule();
56 | $capsule::schema()->dropIfExists($this->tableName);
57 | if ($capsule::schema()->hasTable($this->tableNameV2)) {
58 | $capsule::schema()->rename($this->tableNameV2, $this->tableName);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/lib/munkireport/ArrayToPlist.php:
--------------------------------------------------------------------------------
1 | parser = new CFPropertyList();
21 | }
22 |
23 | public function parse($array)
24 | {
25 | $this->parser->add($dict = new \CFDictionary());
26 | $this->addItem($dict, $array);
27 |
28 | return $this->parser->toXML();
29 | }
30 |
31 |
32 |
33 | // Detect if this is an associative array
34 | public function has_string_keys(array $array)
35 | {
36 | return count(array_filter(array_keys($array), 'is_string')) > 0;
37 | }
38 |
39 |
40 | public function typeValue($value)
41 | {
42 | if (is_numeric($value)) {
43 | return new \CFNumber($value);
44 | } else {
45 | return new \CFString($value);
46 | }
47 | }
48 |
49 | public function addItem(&$dict, $item)
50 | {
51 | $parent = get_class($dict);
52 |
53 | //echo "$parent\n";
54 | foreach ($item as $key => $value) {
55 | if (is_scalar($value)) {
56 | if ($parent == 'CFDictionary') {
57 | $dict->add($key, $this->typeValue($value));
58 | } else {
59 | $dict->add($this->typeValue($value));
60 | }
61 | } else {
62 | if ($this->has_string_keys($value)) {
63 | if ($parent == 'CFArray') {
64 | $dict->add($newdict = new \CFDictionary());
65 | } else {
66 | $dict->add($key, $newdict = new \CFDictionary());
67 | }
68 | } else {
69 | $dict->add($key, $newdict = new \CFArray());
70 | }
71 |
72 | $this->addItem($newdict, $value);
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/database/migrations/2017_02_12_235252_hash.php:
--------------------------------------------------------------------------------
1 | hasTable($this->tableNameV2)) {
17 | // Migration already failed before, but didnt finish
18 | throw new Exception("previous failed migration exists");
19 | }
20 |
21 | if ($capsule::schema()->hasTable($this->tableName)) {
22 | $capsule::schema()->rename($this->tableName, $this->tableNameV2);
23 | $migrateData = true;
24 | }
25 |
26 | $capsule::schema()->create($this->tableName, function (Blueprint $table) {
27 | $table->increments('id');
28 | $table->string('serial_number');
29 | $table->string('name', 50);
30 | $table->string('hash');
31 | $table->bigInteger('timestamp');
32 | });
33 |
34 | if ($migrateData) {
35 | $capsule::unprepared("INSERT INTO
36 | $this->tableName
37 | SELECT
38 | id,
39 | serial,
40 | name,
41 | hash,
42 | timestamp
43 | FROM
44 | $this->tableNameV2");
45 | $capsule::schema()->drop($this->tableNameV2);
46 | }
47 |
48 | // (Re)create indexes
49 | $capsule::schema()->table($this->tableName, function (Blueprint $table) {
50 | $table->index(['serial_number']);
51 | $table->index(['serial_number', 'name']);
52 | });
53 | }
54 |
55 | public function down()
56 | {
57 | $capsule = new Capsule();
58 | $capsule::schema()->dropIfExists($this->tableName);
59 | if ($capsule::schema()->hasTable($this->tableNameV2)) {
60 | $capsule::schema()->rename($this->tableNameV2, $this->tableName);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/public/assets/client_installer/payload/usr/local/munki/report_broken_client:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Broken client reporter
4 | # First argument is the message
5 | # Second argument is the module that is reporting
6 | # Third argument is the severity (danger, warning, info)
7 |
8 | MSG="$1"
9 | if [ -z "$MSG" ]; then
10 | MSG="Unspecified client error"
11 | fi
12 |
13 | # Default module is reportbrokenclient
14 | MODULE="$2"
15 | if [ -z "$MODULE" ]; then
16 | MODULE="reportbrokenclient"
17 | fi
18 |
19 | # Default type is danger, other possible types: warning, info
20 | TYPE="$3"
21 | if [ -z "$TYPE" ]; then
22 | TYPE="danger"
23 | fi
24 |
25 | SERIAL=$(/usr/sbin/ioreg -c IOPlatformExpertDevice | /usr/bin/grep IOPlatformSerialNumber | /usr/bin/awk '{print $4}' | /usr/bin/tr -d '"')
26 | NAME=$(/usr/sbin/scutil --get ComputerName)
27 | OSVERSIONLONG=$(/usr/bin/uname -r) # Returns Darwin version
28 | OSVERS=${osversionlong/.*/}
29 |
30 | # Get pref from foundation if OS > 10.9 (Darwin 13)
31 | if [[ "${OSVERS}" > "13" ]]
32 | then
33 | BASEURL=$(/usr/bin/osascript -l JavaScript -e "ObjC.import('Foundation'); $.CFPreferencesCopyAppValue('BaseUrl', 'MunkiReport');")
34 | PASSPHRASE=$(/usr/bin/osascript -l JavaScript -e "ObjC.import('Foundation'); $.CFPreferencesCopyAppValue('Passphrase', 'MunkiReport');" 2>/dev/null)
35 | else
36 | BASEURL=$(/usr/bin/defaults read /Library/Preferences/MunkiReport BaseUrl)
37 | PASSPHRASE=$(/usr/bin/defaults read /Library/Preferences/MunkiReport Passphrase 2>/dev/null)
38 | fi
39 | SUBMITURL="${BASEURL}/index.php?/report/broken_client"
40 |
41 | # Application paths
42 | CURL="/usr/bin/curl"
43 |
44 | # Check if passphrase is set and submit it
45 | if [[ ${PASSPHRASE} == "" ]] ; then
46 |
47 | $CURL --max-time 5 --silent \
48 | -d msg="$MSG" \
49 | -d module="$MODULE" \
50 | -d type="$TYPE" \
51 | -d serial="$SERIAL" \
52 | -d name="$NAME" \
53 | "$SUBMITURL"
54 |
55 | # Has passphrase
56 | else
57 |
58 | $CURL --max-time 5 --silent \
59 | -d msg="$MSG" \
60 | -d module="$MODULE" \
61 | -d type="$TYPE" \
62 | -d serial="$SERIAL" \
63 | -d passphrase="$PASSPHRASE" \
64 | -d name="$NAME" \
65 | "$SUBMITURL"
66 | fi
67 |
68 | exit 0
69 |
--------------------------------------------------------------------------------
/app/views/dashboard/business_unit.php:
--------------------------------------------------------------------------------
1 | view('partials/head'); ?>
2 |
3 |
22 |
23 |
69 |
--------------------------------------------------------------------------------
/app/Console/Commands/DownCommand.php:
--------------------------------------------------------------------------------
1 | comment('Application is already down.');
39 |
40 | return true;
41 | }
42 |
43 | file_put_contents(storage_path('framework/down'),
44 | json_encode($this->getDownFilePayload(),
45 | JSON_PRETTY_PRINT));
46 |
47 | $this->comment('Application is now in maintenance mode.');
48 | } catch (Exception $e) {
49 | $this->error('Failed to enter maintenance mode.');
50 |
51 | $this->error($e->getMessage());
52 |
53 | return 1;
54 | }
55 | }
56 |
57 | /**
58 | * Get the payload to be placed in the "down" file.
59 | *
60 | * @return array
61 | */
62 | protected function getDownFilePayload()
63 | {
64 | return [
65 | 'time' => $this->currentTime(),
66 | 'message' => $this->option('message'),
67 | // 'retry' => $this->getRetryTime(),
68 | // 'allowed' => $this->option('allow'),
69 | ];
70 | }
71 |
72 | /**
73 | * Get the number of seconds the client should wait before retrying their request.
74 | *
75 | * @return int|null
76 | */
77 | protected function getRetryTime()
78 | {
79 | $retry = $this->option('retry');
80 |
81 | return is_numeric($retry) && $retry > 0 ? (int) $retry : null;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/docs/hacking.md:
--------------------------------------------------------------------------------
1 | Hacking instructions for munkireport-php (at the moment just a braindump)
2 |
3 | ## Views
4 |
5 | All views are in the views folder, if you want to modify the views you can override the view path by adding
6 |
7 | $conf['view_path']
8 |
9 | to config.php and point it to a different location outside the munkireport app directory.
10 |
11 | If you just want to change de dashboard, you can add a custom dashboard:
12 |
13 | app/views/dashboard/custom_dashboard.php
14 |
15 | ## Add more data
16 |
17 | To add new data to munkireport2 you can set up a module. A module is a directory that contains
18 | * an install script for the client (which will gather the appropriate data and point munkireport to it)
19 | * a model (which describes how the data is represented in the database)
20 | * optionally a controller (which you can use to download additional files)
21 |
22 | If you want to understand how modules work, take a look at some modules in the modules directory.
23 |
24 | ## Graphs
25 |
26 | Munkireport comes with a bundled graphing library: flotr2.
27 |
28 | Munkireport comes with a wrapper function around flotr2 that retrieves graph data and plots a graph. The wrapper is called DrawGraph().
29 |
30 | There is also the NVD3 graphing library, based on D3.js. We want to move away from flotr for graphs.
31 |
32 | ### Network pie graph
33 |
34 | If you want to plot where your clients are in the network, you can use the network pie. Global network locations are in config.php.
35 | If you want to use those, just add an empty parameter object (parms = {}).
36 |
37 | // Override network settings in config.php
38 | var parms = {
39 | "Campus": ["145.108.", "130.37."]
40 | };
41 |
42 | drawGraph("", '#ip-plot', pieOptions, parms);
43 |
44 |
45 | ## Variables
46 |
47 | Don't pollute the $GLOBALS array, at the moment there are a handful of variables passed around via $GLOBALS:
48 |
49 | * $GLOBALS['alerts'] - alerts and messages
50 | * $GLOBALS['auth'] - authorization variable (currently only in use for report)
51 | * $GLOBALS['conf'] - config items access with conf()
52 | * $GLOBALS['dbh'] - the database handle
53 | * $GLOBALS['version'] - the current version of MunkiReport
54 |
55 | # Javascript
56 |
57 | A large portion of the UI is based on javascript.
58 |
59 | ## Events
60 |
61 | When the DOM is ready and the language files are loaded, the appReady event is triggered.
62 |
63 | When there is an update in the filters or a refresh for a dashboard, appUpdate is triggered..
64 |
--------------------------------------------------------------------------------
/app/lib/munkireport/LegacyMigrationSupport.php:
--------------------------------------------------------------------------------
1 | capsule){
12 | $this->capsule = new Capsule;
13 |
14 | if( ! $connection = conf('connection')){
15 | die('Database connection not configured');
16 | }
17 |
18 | if(has_mysql_db($connection)){
19 | add_mysql_opts($connection);
20 | }
21 |
22 | $this->capsule->addConnection($connection);
23 | $this->capsule->setAsGlobal();
24 | $this->capsule->bootEloquent();
25 | }
26 |
27 | return $this->capsule;
28 | }
29 |
30 | /**
31 | * Query the `migration` table to retrieve the current model version for a MunkiReport PHP v2 model.
32 | *
33 | * @param $tableName string Name of the v2 table to check migrations for.
34 | *
35 | * @return integer The current version of the legacy table, or null if no such migration exists (model is either
36 | * older than v2 migrations or newer than v3)
37 | */
38 | function getLegacyModelSchemaVersion($tableName) {
39 | $capsule = $this->connectDB();
40 | if (!$capsule::schema()->hasTable('migration')) return null;
41 |
42 | $currentVersion = $capsule::table('migration')
43 | ->where('table_name', '=', $tableName)
44 | ->first();
45 |
46 | if ($currentVersion) {
47 | return $currentVersion->version;
48 | } else {
49 | return null;
50 | }
51 | }
52 |
53 | function setLegacyModelSchemaVersion($tableName, $version) {
54 | $capsule = $this->connectDB();
55 | $capsule::table('migration')
56 | ->where('table_name', $tableName)
57 | ->update(['version' => $version]);
58 | }
59 |
60 | function markLegacyMigrationRan() {
61 | $capsule = $this->connectDB();
62 | $capsule::table('migration')
63 | ->where('table_name', static::$legacyTableName)
64 | ->update(['version' => static::$legacySchemaVersion]);
65 | }
66 |
67 | function markLegacyRollbackRan() {
68 | $capsule = $this->connectDB();
69 | $capsule::table('migration')
70 | ->where('table_name', static::$legacyTableName)
71 | ->update(['version' => static::$legacySchemaVersion - 1]);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/views/system/widget_gallery.php:
--------------------------------------------------------------------------------
1 | view('partials/head'); ?>
2 |
3 |
6 |
7 |