Adobe Connect Bridge Marketplace edition — initial release
This commit is contained in:
@@ -0,0 +1,616 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
|
||||
/**
|
||||
* Free online activation for MeetLet Marketplace edition.
|
||||
*
|
||||
* Hard-locks all plugin features until the administrator completes voluntary
|
||||
* online activation. After activation, every Marketplace feature unlocks.
|
||||
*
|
||||
* @package mod_meetletacbmarketplace
|
||||
* @copyright 2026 MeetLet
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_meetletacbmarketplace\registration;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Activate / validate / deactivate free-tier site registration and gate features.
|
||||
*/
|
||||
class manager {
|
||||
/** Free product slug on the MeetLet license platform. */
|
||||
public const PRODUCT_SLUG = 'free-adobe-connect-bridge';
|
||||
|
||||
public const CONFIG_API_URL = 'registration_api_url';
|
||||
|
||||
public const DEFAULT_API_URL = 'https://license.webatsign.com';
|
||||
|
||||
public static function api_base_url(): string {
|
||||
$url = trim((string)get_config('meetletacbmarketplace', self::CONFIG_API_URL));
|
||||
$url = rtrim($url, '/');
|
||||
return $url !== '' ? $url : self::DEFAULT_API_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the site administrator has completed free-tier registration.
|
||||
*/
|
||||
public static function is_registered(): bool {
|
||||
$row = store::get_record();
|
||||
if (!$row || ($row->status ?? '') !== error_mapper::STATUS_ACTIVE) {
|
||||
return false;
|
||||
}
|
||||
return self::get_verified_authorization() !== null;
|
||||
}
|
||||
|
||||
public static function is_usable(): bool {
|
||||
return self::is_registered();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moodle tabbed admin settings URL for a specific tab.
|
||||
*/
|
||||
public static function settings_tab_url(string $tab): \moodle_url {
|
||||
return new \moodle_url('/admin/settings.php', [
|
||||
'section' => 'modsettingmeetletacbmarketplace',
|
||||
], $tab);
|
||||
}
|
||||
|
||||
public static function settings_url(): \moodle_url {
|
||||
return self::settings_tab_url('meetletacbmarketplace_registration');
|
||||
}
|
||||
|
||||
public static function privacy_tab_url(): \moodle_url {
|
||||
return self::settings_tab_url('meetletacbmarketplace_privacy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw registration_required with continue link to Online activation tab.
|
||||
*/
|
||||
public static function require_registered(): void {
|
||||
if (self::is_registered()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (store::get_authorization() !== null) {
|
||||
self::recheck(true);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Fall through.
|
||||
}
|
||||
if (self::is_registered()) {
|
||||
return;
|
||||
}
|
||||
throw new \moodle_exception('registration_required', 'meetletacbmarketplace', self::settings_url());
|
||||
}
|
||||
|
||||
/** @deprecated Use require_registered() */
|
||||
public static function require_active(): void {
|
||||
self::require_registered();
|
||||
}
|
||||
|
||||
public static function admin_gate_notice_html(): string {
|
||||
if (self::is_registered()) {
|
||||
return '';
|
||||
}
|
||||
$url = self::settings_url();
|
||||
return \html_writer::div(
|
||||
\html_writer::tag('strong', get_string('registration_gate_title', 'meetletacbmarketplace')) .
|
||||
' ' . get_string('registration_gate_body', 'meetletacbmarketplace') . ' ' .
|
||||
\html_writer::link($url, get_string('registration_gate_link', 'meetletacbmarketplace')),
|
||||
'alert alert-warning meetlet-registration-gate mb-3'
|
||||
);
|
||||
}
|
||||
|
||||
public static function admin_hero_banner_html(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
public static function activate_free(): array {
|
||||
return self::activate();
|
||||
}
|
||||
|
||||
public static function activate(): array {
|
||||
if (self::is_registered()) {
|
||||
throw new \moodle_exception('registration_already_active', 'meetletacbmarketplace');
|
||||
}
|
||||
$row = store::get_record();
|
||||
$stale = $row && in_array($row->status ?? '', [
|
||||
error_mapper::STATUS_REVOKED,
|
||||
error_mapper::STATUS_INVALID_KEY,
|
||||
error_mapper::STATUS_EXPIRED,
|
||||
error_mapper::STATUS_OFFLINE_GRACE_EXPIRED,
|
||||
error_mapper::STATUS_SUSPENDED,
|
||||
error_mapper::STATUS_DOMAIN_MISMATCH,
|
||||
error_mapper::STATUS_ACTIVATION_LIMIT,
|
||||
], true);
|
||||
if ($stale) {
|
||||
store::rotate_identity();
|
||||
}
|
||||
return self::activate_with_current_identity(true);
|
||||
}
|
||||
|
||||
public static function validate(bool $force = true): array {
|
||||
return self::validate_or_refresh($force);
|
||||
}
|
||||
|
||||
public static function recheck(bool $force = true): array {
|
||||
return self::validate_or_refresh($force);
|
||||
}
|
||||
|
||||
public static function deactivate(): void {
|
||||
$identity = store::ensure_identity();
|
||||
$signed = self::signed_request($identity, ['action' => 'deactivate']);
|
||||
try {
|
||||
(new api_client())->deactivate($identity->installation_id, $signed);
|
||||
} catch (\Throwable $e) {
|
||||
// Still clear local state.
|
||||
}
|
||||
store::update([
|
||||
'auth_json' => null,
|
||||
'license_uuid' => null,
|
||||
'status' => 'inactive',
|
||||
'last_error' => null,
|
||||
]);
|
||||
store::rotate_identity();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function status_summary(): array {
|
||||
self::enforce_local_expiry();
|
||||
$row = store::get_record();
|
||||
$storedstatus = (string)($row->status ?? 'inactive');
|
||||
$hardblock = in_array($storedstatus, [
|
||||
error_mapper::STATUS_SUSPENDED,
|
||||
error_mapper::STATUS_REVOKED,
|
||||
error_mapper::STATUS_EXPIRED,
|
||||
error_mapper::STATUS_INVALID_KEY,
|
||||
error_mapper::STATUS_DOMAIN_MISMATCH,
|
||||
error_mapper::STATUS_ACTIVATION_LIMIT,
|
||||
], true);
|
||||
$lasterror = store::get_last_error();
|
||||
$auth = $hardblock ? null : self::get_verified_authorization();
|
||||
$payload = is_array($auth['payload'] ?? null) ? $auth['payload'] : null;
|
||||
$status = $storedstatus;
|
||||
if ($payload !== null && $auth !== null) {
|
||||
if ($status !== error_mapper::STATUS_ACTIVE) {
|
||||
store::update([
|
||||
'status' => error_mapper::STATUS_ACTIVE,
|
||||
'last_error' => null,
|
||||
]);
|
||||
}
|
||||
$status = error_mapper::STATUS_ACTIVE;
|
||||
} else if ($auth === null && $status === error_mapper::STATUS_ACTIVE) {
|
||||
$status = error_mapper::STATUS_INACTIVE;
|
||||
}
|
||||
$active = $auth !== null && !$hardblock;
|
||||
|
||||
return [
|
||||
'active' => $active,
|
||||
'registered' => $active,
|
||||
'status' => $status,
|
||||
'status_label' => self::status_label($active ? error_mapper::STATUS_ACTIVE : $status),
|
||||
'product' => $active ? self::PRODUCT_SLUG : '',
|
||||
'product_label' => $active ? get_string('registration_product_free', 'meetletacbmarketplace') : '',
|
||||
'can_deactivate' => self::can_deactivate($row),
|
||||
'auth_expires_at' => self::parse_unix_timestamp($payload['expires_at'] ?? null),
|
||||
'features' => is_array($payload['features'] ?? null) ? $payload['features'] : [],
|
||||
'installation_id' => $row->installation_id ?? '',
|
||||
'domain' => self::site_domain(),
|
||||
'api_url' => self::api_base_url(),
|
||||
'last_validated' => !empty($row->last_validated) ? (int)$row->last_validated : null,
|
||||
'error' => $active ? null : self::status_error_message($status, $lasterror),
|
||||
'last_error' => $lasterror,
|
||||
'license_uuid' => $row->license_uuid ?? null,
|
||||
'site_company' => self::site_company(),
|
||||
'site_admin_email' => self::site_admin_email(),
|
||||
'moodle_release' => self::moodle_release(),
|
||||
'plugin_release' => self::plugin_release(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function heartbeat(): void {
|
||||
$row = store::get_record();
|
||||
if (!$row || empty($row->auth_json)) {
|
||||
mtrace('MeetLet registration: not registered — skip heartbeat.');
|
||||
return;
|
||||
}
|
||||
if (self::enforce_local_expiry()) {
|
||||
mtrace('MeetLet registration: authorization expired — cleared auth.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
self::validate_or_refresh(true);
|
||||
mtrace('MeetLet registration: heartbeat OK.');
|
||||
} catch (\Throwable $e) {
|
||||
mtrace('MeetLet registration heartbeat failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static function site_domain(): string {
|
||||
global $CFG;
|
||||
$host = parse_url($CFG->wwwroot ?? '', PHP_URL_HOST);
|
||||
return self::normalize_domain(is_string($host) ? $host : 'localhost');
|
||||
}
|
||||
|
||||
public static function normalize_domain(string $input): string {
|
||||
$host = trim(strtolower($input));
|
||||
if ($host === '') {
|
||||
return 'localhost';
|
||||
}
|
||||
if (preg_match('~(://|/|\?|#)~', $host)) {
|
||||
$parsed = parse_url((strpos($host, '://') === false ? 'https://' : '') . $host, PHP_URL_HOST);
|
||||
$host = is_string($parsed) ? strtolower($parsed) : $host;
|
||||
}
|
||||
$host = rtrim($host, '.');
|
||||
if (strpos($host, ':') !== false) {
|
||||
$host = explode(':', $host, 2)[0];
|
||||
}
|
||||
if (str_starts_with($host, 'www.')) {
|
||||
$host = substr($host, 4);
|
||||
}
|
||||
if ($host !== '' && function_exists('idn_to_ascii') && preg_match('/[^\x20-\x7f]/', $host)) {
|
||||
$ascii = idn_to_ascii($host, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46);
|
||||
if (is_string($ascii) && $ascii !== '') {
|
||||
$host = strtolower($ascii);
|
||||
}
|
||||
}
|
||||
return $host !== '' ? $host : 'localhost';
|
||||
}
|
||||
|
||||
public static function status_label(string $status): string {
|
||||
$key = 'registration_status_' . $status;
|
||||
if (get_string_manager()->string_exists($key, 'meetletacbmarketplace')) {
|
||||
return get_string($key, 'meetletacbmarketplace');
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
public static function parse_unix_timestamp($value): ?int {
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
if (is_int($value) || is_float($value)) {
|
||||
$n = (int) $value;
|
||||
} else if (is_string($value) && is_numeric($value)) {
|
||||
$n = (int) $value;
|
||||
} else if (is_string($value)) {
|
||||
$parsed = strtotime($value);
|
||||
return ($parsed !== false && $parsed > 0) ? $parsed : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if ($n > 9999999999) {
|
||||
$n = (int) floor($n / 1000);
|
||||
}
|
||||
return $n > 0 ? $n : null;
|
||||
}
|
||||
|
||||
public static function enforce_local_expiry(): bool {
|
||||
$auth = store::get_authorization();
|
||||
if ($auth === null) {
|
||||
return false;
|
||||
}
|
||||
if (!crypto::verify(crypto::PLATFORM_PUBLIC_KEY_PEM, $auth['payload'], $auth['signature'])) {
|
||||
return false;
|
||||
}
|
||||
$expires = self::parse_unix_timestamp($auth['payload']['expires_at'] ?? null);
|
||||
if ($expires !== null && time() >= $expires) {
|
||||
self::invalidate_local([
|
||||
'code' => 'LICENSE_EXPIRED',
|
||||
'message' => get_string('registration_err_expired', 'meetletacbmarketplace'),
|
||||
'at' => time(),
|
||||
], error_mapper::STATUS_EXPIRED);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{payload:array,signature:string}|null
|
||||
*/
|
||||
public static function get_verified_authorization(): ?array {
|
||||
$row = store::get_record();
|
||||
if ($row && in_array($row->status ?? '', [
|
||||
error_mapper::STATUS_SUSPENDED,
|
||||
error_mapper::STATUS_REVOKED,
|
||||
error_mapper::STATUS_EXPIRED,
|
||||
error_mapper::STATUS_INVALID_KEY,
|
||||
error_mapper::STATUS_DOMAIN_MISMATCH,
|
||||
error_mapper::STATUS_ACTIVATION_LIMIT,
|
||||
], true)) {
|
||||
return null;
|
||||
}
|
||||
$auth = store::get_authorization();
|
||||
if ($auth === null) {
|
||||
return null;
|
||||
}
|
||||
if (!crypto::verify(crypto::PLATFORM_PUBLIC_KEY_PEM, $auth['payload'], $auth['signature'])) {
|
||||
return null;
|
||||
}
|
||||
$expires = self::parse_unix_timestamp($auth['payload']['expires_at'] ?? null);
|
||||
if ($expires !== null && time() >= $expires) {
|
||||
return null;
|
||||
}
|
||||
return $auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \stdClass|null $row
|
||||
*/
|
||||
public static function can_deactivate(?\stdClass $row = null): bool {
|
||||
$row = $row ?? store::get_record();
|
||||
if (!$row) {
|
||||
return false;
|
||||
}
|
||||
if (!empty($row->auth_json)) {
|
||||
return true;
|
||||
}
|
||||
return in_array($row->status ?? '', [
|
||||
error_mapper::STATUS_ACTIVE,
|
||||
error_mapper::STATUS_GRACE,
|
||||
error_mapper::STATUS_EXPIRED,
|
||||
error_mapper::STATUS_OFFLINE_GRACE_EXPIRED,
|
||||
error_mapper::STATUS_SUSPENDED,
|
||||
error_mapper::STATUS_REVOKED,
|
||||
error_mapper::STATUS_INVALID_KEY,
|
||||
error_mapper::STATUS_DOMAIN_MISMATCH,
|
||||
error_mapper::STATUS_ACTIVATION_LIMIT,
|
||||
], true);
|
||||
}
|
||||
|
||||
protected static function validate_or_refresh(bool $force = false): array {
|
||||
self::enforce_local_expiry();
|
||||
$identity = store::ensure_identity();
|
||||
$auth = store::get_authorization();
|
||||
$now = time();
|
||||
if (!$force && $auth && !empty($auth['payload']['expires_at'])) {
|
||||
$expires = self::parse_unix_timestamp($auth['payload']['expires_at']);
|
||||
if ($expires !== null && $now < ($expires - 3600) && self::get_verified_authorization() !== null) {
|
||||
return $auth;
|
||||
}
|
||||
}
|
||||
|
||||
$api = new api_client();
|
||||
if (!$force && $auth) {
|
||||
try {
|
||||
$signedrefresh = self::signed_request($identity, new \stdClass());
|
||||
$data = $api->refresh($signedrefresh);
|
||||
return self::persist_authorization($data, $identity);
|
||||
} catch (\Throwable $e) {
|
||||
$mapped = self::apply_mapped_error($e, false);
|
||||
if (!empty($mapped['clear_auth'])) {
|
||||
throw registration_exception::from_throwable($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$body = [
|
||||
'product' => self::PRODUCT_SLUG,
|
||||
'domain' => self::site_domain(),
|
||||
'reported_host' => self::site_domain(),
|
||||
'current_version' => self::plugin_release(),
|
||||
];
|
||||
$signed = self::signed_request($identity, $body);
|
||||
try {
|
||||
$data = $api->validate($signed);
|
||||
} catch (\Throwable $e) {
|
||||
self::apply_mapped_error($e);
|
||||
if (self::get_verified_authorization() !== null) {
|
||||
store::update(['last_validated' => $now]);
|
||||
$cached = self::get_verified_authorization();
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
throw registration_exception::from_throwable($e);
|
||||
}
|
||||
return self::persist_authorization($data, $identity);
|
||||
}
|
||||
|
||||
protected static function activate_with_current_identity(bool $allowrotate): array {
|
||||
$identity = store::ensure_identity();
|
||||
$body = [
|
||||
'product' => self::PRODUCT_SLUG,
|
||||
'installation_id' => $identity->installation_id,
|
||||
'installation_public_key' => $identity->public_key_pem,
|
||||
'domain' => self::site_domain(),
|
||||
'environment' => self::environment(),
|
||||
'platform' => 'moodle',
|
||||
'platform_version' => self::moodle_release(),
|
||||
'runtime' => 'php',
|
||||
'runtime_version' => PHP_VERSION,
|
||||
'metadata' => [
|
||||
'plugin' => 'mod_meetletacbmarketplace',
|
||||
'plugin_release' => self::plugin_release(),
|
||||
],
|
||||
];
|
||||
$company = self::site_company();
|
||||
$email = self::site_admin_email();
|
||||
if ($company !== '') {
|
||||
$body['metadata']['company'] = $company;
|
||||
}
|
||||
if ($email !== '') {
|
||||
$body['metadata']['email'] = $email;
|
||||
$body['metadata']['admin_email'] = $email;
|
||||
}
|
||||
try {
|
||||
$data = (new api_client())->activate($body);
|
||||
} catch (\Throwable $e) {
|
||||
$ex = registration_exception::from_throwable($e);
|
||||
$code = $ex->get_error_code();
|
||||
if ($allowrotate && in_array($code, ['INVALID_LICENSE', 'INSTALLATION_REVOKED'], true)) {
|
||||
store::rotate_identity();
|
||||
return self::activate_with_current_identity(false);
|
||||
}
|
||||
self::apply_mapped_error($ex);
|
||||
throw $ex;
|
||||
}
|
||||
return self::persist_authorization($data, store::ensure_identity());
|
||||
}
|
||||
|
||||
protected static function persist_authorization(array $data, \stdClass $identity): array {
|
||||
$auth = $data['authorization'] ?? null;
|
||||
if (!is_array($auth) || empty($auth['payload']) || empty($auth['signature'])) {
|
||||
$ex = new registration_exception('INVALID_REQUEST', 'Missing authorization in API response');
|
||||
self::apply_mapped_error($ex);
|
||||
throw $ex;
|
||||
}
|
||||
if (!crypto::verify(crypto::PLATFORM_PUBLIC_KEY_PEM, $auth['payload'], $auth['signature'])) {
|
||||
$ex = new registration_exception(
|
||||
error_mapper::LOCAL_INVALID_AUTHORIZATION_SIGNATURE,
|
||||
'Authorization signature verification failed'
|
||||
);
|
||||
self::apply_mapped_error($ex);
|
||||
throw $ex;
|
||||
}
|
||||
$licenseuuid = $data['license_id'] ?? ($auth['payload']['license_id'] ?? null);
|
||||
store::update([
|
||||
'auth_json' => json_encode($auth, JSON_UNESCAPED_SLASHES),
|
||||
'license_uuid' => $licenseuuid,
|
||||
'status' => error_mapper::STATUS_ACTIVE,
|
||||
'last_error' => null,
|
||||
'last_validated' => time(),
|
||||
]);
|
||||
return $auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string|null $error
|
||||
*/
|
||||
public static function invalidate_local($error, string $status = 'inactive'): void {
|
||||
$fields = [
|
||||
'auth_json' => null,
|
||||
'status' => $status,
|
||||
'last_validated' => time(),
|
||||
];
|
||||
if (is_array($error)) {
|
||||
$fields['last_error'] = json_encode([
|
||||
'code' => (string)($error['code'] ?? 'UNKNOWN'),
|
||||
'message' => (string)($error['message'] ?? ''),
|
||||
'details' => $error['details'] ?? null,
|
||||
'request_id' => $error['request_id'] ?? null,
|
||||
'at' => (int)($error['at'] ?? time()),
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
} else {
|
||||
$fields['last_error'] = $error;
|
||||
}
|
||||
store::update($fields);
|
||||
}
|
||||
|
||||
protected static function apply_mapped_error(\Throwable $e, bool $persist = true): array {
|
||||
$hasgrace = self::get_verified_authorization() !== null;
|
||||
$ex = registration_exception::from_throwable($e);
|
||||
$mapped = error_mapper::from_exception($ex, $hasgrace);
|
||||
if (!$persist) {
|
||||
return $mapped;
|
||||
}
|
||||
$lasterror = $ex->to_last_error();
|
||||
$lasterror['message'] = $mapped['message'] ?? $lasterror['message'];
|
||||
if (!empty($mapped['request_id'])) {
|
||||
$lasterror['request_id'] = $mapped['request_id'];
|
||||
}
|
||||
if (!empty($mapped['clear_auth'])) {
|
||||
self::invalidate_local($lasterror, $mapped['status']);
|
||||
return $mapped;
|
||||
}
|
||||
store::update([
|
||||
'status' => $mapped['status'],
|
||||
'last_error' => json_encode($lasterror, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
||||
'last_validated' => time(),
|
||||
]);
|
||||
return $mapped;
|
||||
}
|
||||
|
||||
protected static function signed_request(\stdClass $identity, $body): array {
|
||||
$timestamp = time();
|
||||
$nonce = crypto::random_nonce();
|
||||
$bodyforcanon = (is_array($body) && $body === []) ? new \stdClass() : $body;
|
||||
$parts = [
|
||||
'installation_id' => $identity->installation_id,
|
||||
'timestamp' => $timestamp,
|
||||
'nonce' => $nonce,
|
||||
'body' => $bodyforcanon,
|
||||
];
|
||||
$signature = crypto::sign($identity->private_key_pem, $parts);
|
||||
$out = [
|
||||
'installation_id' => $identity->installation_id,
|
||||
'timestamp' => $timestamp,
|
||||
'nonce' => $nonce,
|
||||
'signature' => $signature,
|
||||
];
|
||||
if (is_array($body)) {
|
||||
foreach ($body as $k => $v) {
|
||||
$out[$k] = $v;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $lasterror
|
||||
*/
|
||||
protected static function status_error_message(string $status, ?array $lasterror): ?string {
|
||||
if ($status === error_mapper::STATUS_EXPIRED) {
|
||||
return get_string('registration_err_expired', 'meetletacbmarketplace');
|
||||
}
|
||||
return $lasterror['message'] ?? null;
|
||||
}
|
||||
|
||||
protected static function site_company(): string {
|
||||
global $SITE;
|
||||
$name = trim(strip_tags((string)($SITE->fullname ?? '')));
|
||||
if ($name === '') {
|
||||
$name = trim(strip_tags((string)($SITE->shortname ?? '')));
|
||||
}
|
||||
return $name !== '' ? $name : self::site_domain();
|
||||
}
|
||||
|
||||
protected static function site_admin_email(): string {
|
||||
global $CFG, $USER;
|
||||
$candidates = [trim((string)($CFG->supportemail ?? ''))];
|
||||
$admin = get_admin();
|
||||
if ($admin && !empty($admin->email)) {
|
||||
$candidates[] = trim((string)$admin->email);
|
||||
}
|
||||
if (!empty($USER->email)) {
|
||||
$candidates[] = trim((string)$USER->email);
|
||||
}
|
||||
foreach ($candidates as $email) {
|
||||
if ($email !== '' && validate_email($email)) {
|
||||
return $email;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
protected static function environment(): string {
|
||||
global $CFG;
|
||||
if (!empty($CFG->debugdeveloper)) {
|
||||
return 'development';
|
||||
}
|
||||
return 'production';
|
||||
}
|
||||
|
||||
protected static function moodle_release(): string {
|
||||
global $CFG;
|
||||
return isset($CFG->release) ? (string)$CFG->release : '';
|
||||
}
|
||||
|
||||
public static function plugin_release(): string {
|
||||
global $CFG;
|
||||
$plugin = new \stdClass();
|
||||
$versionfile = $CFG->dirroot . '/mod/meetletacbmarketplace/version.php';
|
||||
if (is_readable($versionfile)) {
|
||||
include($versionfile);
|
||||
}
|
||||
if (!empty($plugin->release)) {
|
||||
return (string)$plugin->release;
|
||||
}
|
||||
$ver = get_config('meetletacbmarketplace', 'version');
|
||||
return $ver !== false ? (string)$ver : '0';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user