#!/usr/bin/php
<?php
/**
 * Copyright (c) Luca Saba <lucasaba@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 **/

$CJ_VERSION = '1.0.0';

$shortopts = "V::v::c::p:h";
$longopts = array("verbose::", "version::", "critical::", "path:");

$options = getopt($shortopts, $longopts);

if (isset($options['V'])) {
    echo "check_joomla Nagios plugin $CJ_VERSION (c) Luca Saba <lucasaba\@gmail.com>\n";
    exit(0);
}

check($options, $CJ_VERSION);

function check($options, $CJ_VERSION) {
    if (isset($options['h']) || (!isset($options['p']) && !isset($options['path']))) {
        echo "

 check_joomla Nagios plugin $CJ_VERSION (c) Luca Saba <lucasaba\@gmail.com>

 Usage: check_plugin [-w=<warn>] [-c=<crit>] [-V] [-v] [-h] -p=<path_to_joomla_root>

 -p, --path=INTEGER
    Path to Joomla root folder
 -c, --critical
    check_joomla fires a warning as default. If you want to rise a critical error, use this option
 -V, --version
    Output version information for the plugin
 -v, --verbose
    Enable verbose output
 -h, --help
    Show this help\n";
        exit(0);
    }

    $verbose = isset($options['v']);

    $path = (isset($options['p'])) ? $options['p'] : $options['path'];

    //Check if path ends with directory separator
    if(substr($path, -1, 1) != DIRECTORY_SEPARATOR) {
        $path .= DIRECTORY_SEPARATOR;
    }

    $current = getCurrentVersion($path, $verbose);

    $exitCode = calculateUpdate($current, $verbose);

    if($exitCode === 0) {
        echo sprintf("No update available for your current Joomla version: %s.%s.%s\n",
            $current['MAJOR'],
            $current['MINOR'],
            $current['PATCH']
        );
    }

    exit($exitCode);
}

// Extracts the current Joomla version
function getCurrentVersion($path, $verbose = false)
{
    //Check if path exists
    $joomlaPrev38versionFile = 'libraries/cms/version/version.php';
    $joomlaPost38versionFile = 'libraries/src/Version.php';

    if ($verbose) {
        echo "Looking for version.php file inside Joomla\n";
    }

    if (file_exists($path . $joomlaPrev38versionFile)) {
        return getPrev38Version($path . $joomlaPrev38versionFile, $verbose);
    } elseif (file_exists($path . $joomlaPost38versionFile)) {
        return getPost38Version($path . $joomlaPost38versionFile, $verbose);
    } else {
        echo $path . "\n";
        echo "Joomla root folder not found";
        exit(3);
    }
}

function getPrev38Version($path, $verbose)
{
    if ($verbose) {
        echo "Joomla's pre 3.8 version.php file found\n";
    }

    $version_file = file_get_contents($path);
    preg_match_all('/RELEASE\s=\s\'[0-9]\.[0-9]{1,2}\';/', $version_file, $matches);
    if (!isset($matches[0][0])) {
        echo "I couldn't find your Joomla's RELEASE...check that $path exists and is readable\n";
        exit(3);
    }
    preg_match_all("/[0-9]+\.[0-9]+/", $matches[0][0], $rel_match);
    $RELEASE = $rel_match[0][0];

    if ($verbose) {
        echo "Extracted Joomla's release number: $RELEASE\n";
    }
    preg_match_all('/DEV_LEVEL\s=\s\'[0-9]{1,2}\';/', $version_file, $matches);
    if (!isset($matches[0][0])) {
        echo "I couldn't find your Joomla's DEV_LEVEL...check that $path exists and is readable\n";
        exit(3);
    }
    preg_match_all("/[0-9]+/", $matches[0][0], $dev_match);
    $DEV_LEVEL = $dev_match[0][0];

    if ($verbose) {
        echo "Extracted Joomla's dev level number: $DEV_LEVEL\n";
    }

    $version = explode('.', $RELEASE);
    $result = [
        'MAJOR' => $version[0],
        'MINOR' => $version[1],
        'PATCH' => $DEV_LEVEL
    ];

    if (empty($result['MAJOR']) || empty($result['MINOR']) || empty($result['PATCH'])) {
        echo "I couldn't find your Joomla's DEV_LEVEL...check that $path exists and is readable\n";
        exit(3);
    }

    return $result;
}

function getPost38Version($path, $verbose)
{
    if ($verbose) {
        echo "Joomla's post 3.8 version.php file found\n";
    }

    $version_file = file_get_contents($path);
    preg_match_all('/MAJOR_VERSION\s=\s[0-9]+;/', $version_file, $matches);
    if (!isset($matches[0][0])) {
        echo "I couldn't find the your Joomla's MAJOR_VERSION...check that $path exists and is readable\n";
        exit(3);
    }
    preg_match_all("/[0-9]+/", $matches[0][0], $rel_match);
    $MAJOR = $rel_match[0][0];
    if ($verbose) {
        echo "Extracted Joomla's Major value: $MAJOR\n";
    }

    preg_match_all('/MINOR_VERSION\s=\s[0-9]+;/', $version_file, $matches);
    if (!isset($matches[0][0])) {
        echo "I couldn't find the your Joomla's MINOR_VERSION...check that $path exists and is readable\n";
        exit(3);
    }
    preg_match_all("/[0-9]+/", $matches[0][0], $rel_match);
    $MINOR = $rel_match[0][0];
    if ($verbose) {
        echo "Extracted Joomla's Minor value: $MINOR\n";
    }

    preg_match_all('/PATCH_VERSION\s=\s[0-9]+;/', $version_file, $matches);
    if (!isset($matches[0][0])) {
        echo "I couldn't find the your Joomla's PATCH_VERSION...check that $path exists and is readable\n";
        exit(3);
    }
    preg_match_all("/[0-9]+/", $matches[0][0], $rel_match);
    $PATCH = $rel_match[0][0];
    if ($verbose) {
        echo "Extracted Joomla's Patch value: $PATCH\n";
    }

    $result = [
        'MAJOR' => $MAJOR,
        'MINOR' => $MINOR,
        'PATCH' => $PATCH
    ];

    if (null === $result['MAJOR'] || null === $result['MINOR'] || null === $result['PATCH']) {
        echo "I couldn't find your Joomla's version...check that $path exists and is readable\n";
        exit(3);
    }

    return $result;
}

function calculateUpdate($result, $verbose = false, $content = null)
{
    if ($verbose) {
        echo "Getting last updated from Joomla...\n";
    }
    $update_file = simplexml_load_file('https://update.joomla.org/core/list.xml');
    if (null !== $content) {
        $update_file = simplexml_load_string($content);
    }

    if ($update_file === false) {
        echo "Unable to fetch update's list from joomla";
        exit(3);
    } else {
        if ($verbose) {
            echo "Updates fetched. Analyzing...\n";
        }
    }

    $release = sprintf('%s.%s', $result['MAJOR'], $result['MINOR']);
    $specific = sprintf('%s.%s', $release, $result['PATCH']);
    $patch = $result['PATCH'];

    // Test against specific
    if($verbose) {
        echo "Checking for updates for your specific joomla version\n";
    }
    foreach ($update_file->children() as $extension) {
        if ($extension['targetplatformversion']->__toString() === $specific) {
            echo "Joomla update available. Installed version: $specific. Can be updated to ".$extension['version']."\n";
            if(isset($options['c']) || isset($options['critical'])) {
                return 2;
            } else {
                return 1;
            }
        }
    }

    // Test against release
    if($verbose) {
        echo "Nothing specific found. Checking if your release needs an update...\n";
    }

    foreach ($update_file->children() as $extension) {
        if ($extension['targetplatformversion']->__toString() === $release) {
            $values = explode('.', $extension['version']->__toString());
            $newPatch = count($values) === 3 ? intval($values[2]) : 0;
            if ($extension['version']->__toString() === $specific) {
                return 0;
            }
            if ($newPatch <= $patch) {
                return 0;
            }
            echo "Joomla update available. Installed version $specific can be updated to ".$extension['version']."\n";
            if(isset($options['c']) || isset($options['critical'])) {
                return 2;
            } else {
                return 1;
            }
        }
    }

    return 0;
}