memories/lib/Util.php

486 lines
14 KiB
PHP
Raw Permalink Normal View History

2022-08-20 02:53:21 +00:00
<?php
2022-10-19 17:10:36 +00:00
2022-08-20 02:53:21 +00:00
declare(strict_types=1);
namespace OCA\Memories;
use OC\Files\Search\SearchBinaryOperator;
use OC\Files\Search\SearchComparison;
use OC\Files\Search\SearchQuery;
use OCA\Memories\AppInfo\Application;
2022-12-08 21:00:53 +00:00
use OCP\App\IAppManager;
use OCP\Files\Node;
use OCP\Files\Search\ISearchBinaryOperator;
use OCP\Files\Search\ISearchComparison;
2022-12-08 21:00:53 +00:00
use OCP\IConfig;
2022-10-19 17:10:36 +00:00
class Util
{
use UtilController;
public const ARCHIVE_FOLDER = '.archive';
2022-09-25 23:02:26 +00:00
2022-11-09 09:23:12 +00:00
/**
* Get host CPU architecture (amd64 or aarch64).
*
* @psalm-return 'aarch64'|'amd64'|null
2022-11-09 09:23:12 +00:00
*/
public static function getArch(): ?string
2022-11-09 09:23:12 +00:00
{
$uname = strtolower(php_uname('m') ?: 'unknown');
if (str_contains($uname, 'aarch64') || str_contains($uname, 'arm64')) {
2022-11-09 09:23:12 +00:00
return 'aarch64';
}
if (str_contains($uname, 'x86_64') || str_contains($uname, 'amd64')) {
2022-11-09 09:23:12 +00:00
return 'amd64';
}
return null;
}
/**
* Get the libc type for host (glibc or musl).
*
* @psalm-return 'glibc'|'musl'|null
2022-11-09 09:23:12 +00:00
*/
public static function getLibc(): ?string
2022-11-09 09:23:12 +00:00
{
/** @psalm-suppress ForbiddenCode */
$ldd = strtolower(shell_exec('ldd --version 2>&1') ?: 'unknown');
if (str_contains($ldd, 'musl')) {
return 'musl';
}
if (str_contains($ldd, 'glibc')) {
return 'glibc';
2022-11-09 09:23:12 +00:00
}
return null;
}
2022-10-27 20:45:03 +00:00
/**
* Check if albums are enabled for this user.
*/
public static function albumsIsEnabled(): bool
2022-10-27 20:45:03 +00:00
{
$appManager = \OC::$server->get(IAppManager::class);
2022-10-27 20:45:03 +00:00
if (!$appManager->isEnabledForUser('photos')) {
return false;
}
2022-11-29 16:57:03 +00:00
$v = $appManager->getAppVersion('photos');
2022-10-27 20:45:03 +00:00
return version_compare($v, '1.7.0', '>=');
}
/**
* Check if tags is enabled for this user.
*/
public static function tagsIsEnabled(): bool
2022-10-27 20:45:03 +00:00
{
return \OC::$server->get(IAppManager::class)->isEnabledForUser('systemtags');
2022-10-27 20:45:03 +00:00
}
/**
* Check if recognize is enabled for this user.
*/
public static function recognizeIsEnabled(): bool
{
if (!self::recognizeIsInstalled()) {
return false;
}
$config = \OC::$server->get(IConfig::class);
if ('true' !== $config->getAppValue('recognize', 'faces.enabled', 'false')) {
return false;
}
return true;
}
/**
* Check if recognize is installed.
*/
public static function recognizeIsInstalled(): bool
2022-10-27 20:45:03 +00:00
{
$appManager = \OC::$server->get(IAppManager::class);
2022-10-27 20:45:03 +00:00
if (!$appManager->isEnabledForUser('recognize')) {
return false;
}
2022-11-29 16:57:03 +00:00
$v = $appManager->getAppVersion('recognize');
if (!version_compare($v, '3.8.0', '>=')) {
return false;
}
return true;
2022-10-27 20:45:03 +00:00
}
2022-12-08 21:00:53 +00:00
/**
* Check if Face Recognition is enabled by the user.
*/
public static function facerecognitionIsEnabled(): bool
2022-12-08 21:00:53 +00:00
{
if (!self::facerecognitionIsInstalled()) {
return false;
}
try {
return 'true' === \OC::$server->get(IConfig::class)
->getUserValue(self::getUID(), 'facerecognition', 'enabled', 'false')
;
} catch (\Exception) {
// not logged in
}
return false;
2022-12-08 21:00:53 +00:00
}
/**
* Check if Face Recognition is installed and enabled for this user.
*/
public static function facerecognitionIsInstalled(): bool
2022-12-08 21:00:53 +00:00
{
$appManager = \OC::$server->get(IAppManager::class);
2022-12-08 21:00:53 +00:00
if (!$appManager->isEnabledForUser('facerecognition')) {
return false;
}
$v = $appManager->getAppInfo('facerecognition')['version'];
return version_compare($v, '0.9.10-beta.2', '>=');
}
/**
* Check if preview generator is installed.
*/
public static function previewGeneratorIsEnabled(): bool
{
return \OC::$server->get(IAppManager::class)->isEnabledForUser('previewgenerator');
}
/**
* Check if link sharing is allowed.
*
* @todo Check if link sharing is enabled to show the button
*
* @psalm-suppress PossiblyUnusedMethod
*/
public static function isLinkSharingEnabled(): bool
{
$config = \OC::$server->get(IConfig::class);
// Check if the shareAPI is enabled
if ('yes' !== $config->getAppValue('core', 'shareapi_enabled', 'yes')) {
return false;
}
// Check whether public sharing is enabled
if ('yes' !== $config->getAppValue('core', 'shareapi_allow_links', 'yes')) {
return false;
}
return true;
}
/**
* Force a fileinfo value on a node.
* This is a hack to avoid subclassing everything.
*
* @param Node $node File to patch
* @param string $key Key to set
* @param mixed $value Value to set
*/
public static function forceFileInfo(Node &$node, string $key, mixed $value): void
{
/** @var \OC\Files\Node\Node */
$node = $node;
/** @psalm-suppress UndefinedInterfaceMethod */
$node->getFileInfo()[$key] = $value;
}
/**
* Force permissions on a node.
*
* @param Node $node File to patch
* @param int $permissions Permissions to set
*/
public static function forcePermissions(Node &$node, int $permissions): void
{
self::forceFileInfo($node, 'permissions', $permissions);
}
/**
* Convert permissions to string.
*/
public static function permissionsToStr(int $permissions): string
{
$str = '';
if ($permissions & \OCP\Constants::PERMISSION_CREATE) {
$str .= 'C';
}
if ($permissions & \OCP\Constants::PERMISSION_READ) {
$str .= 'R';
}
if ($permissions & \OCP\Constants::PERMISSION_UPDATE) {
$str .= 'U';
}
if ($permissions & \OCP\Constants::PERMISSION_DELETE) {
$str .= 'D';
}
if ($permissions & \OCP\Constants::PERMISSION_SHARE) {
$str .= 'S';
}
return $str;
}
/**
* Add OG metadata to a page for a node.
*
* @param Node $node Node to get metadata from
* @param string $title Title of the page
* @param string $url URL of the page
* @param array $previewArgs Preview arguments (e.g. token)
*/
public static function addOgMetadata(Node $node, string $title, string $url, array $previewArgs): void
{
// Add title
\OCP\Util::addHeader('meta', ['property' => 'og:title', 'content' => $title]);
// Get first node if folder
if ($node instanceof \OCP\Files\Folder) {
if (null === ($node = self::getAnyMedia($node))) {
return; // no media in folder
}
}
// Add file type
$mimeType = $node->getMimeType();
if (str_starts_with($mimeType, 'image/')) {
\OCP\Util::addHeader('meta', ['property' => 'og:type', 'content' => 'image']);
} elseif (str_starts_with($mimeType, 'video/')) {
\OCP\Util::addHeader('meta', ['property' => 'og:type', 'content' => 'video']);
}
// Add OG url
\OCP\Util::addHeader('meta', ['property' => 'og:url', 'content' => $url]);
// Get URL generator
$urlGenerator = \OC::$server->get(\OCP\IURLGenerator::class);
// Add OG image
$preview = $urlGenerator->linkToRouteAbsolute('memories.Image.preview', array_merge($previewArgs, [
'id' => $node->getId(),
'x' => 1024,
'y' => 1024,
'a' => true,
]));
\OCP\Util::addHeader('meta', ['property' => 'og:image', 'content' => $preview]);
}
/**
* Get a random image or video from a given folder.
*/
public static function getAnyMedia(\OCP\Files\Folder $folder): ?Node
{
$query = new SearchQuery(new SearchBinaryOperator(ISearchBinaryOperator::OPERATOR_OR, [
new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', 'image/%'),
new SearchComparison(ISearchComparison::COMPARE_LIKE, 'mimetype', 'video/%'),
]), 1, 0, [], null);
$nodes = $folder->search($query);
if (0 === \count($nodes)) {
return null;
}
return $nodes[0];
}
/**
* Check if any encryption is enabled that we can not cope with
* such as end-to-end encryption.
*/
2022-12-04 17:33:20 +00:00
public static function isEncryptionEnabled(): bool
{
$encryptionManager = \OC::$server->get(\OCP\Encryption\IManager::class);
if ($encryptionManager->isEnabled()) {
// Server-side encryption (OC_DEFAULT_MODULE) is okay, others like e2e are not
return 'OC_DEFAULT_MODULE' !== $encryptionManager->getDefaultEncryptionModuleId();
}
return false;
}
2023-01-04 20:32:36 +00:00
2023-02-06 03:46:44 +00:00
/**
* Check if geolocation (places) is enabled and available.
* Returns the type of the GIS.
*/
public static function placesGISType(): int
{
return self::getSystemConfig('memories.gis_type');
}
/**
* Get list of timeline paths as array.
*
* @return string[] List of paths
*/
public static function getTimelinePaths(string $uid): array
{
$paths = \OC::$server->get(IConfig::class)
->getUserValue($uid, Application::APPNAME, 'timelinePath', null)
?: self::getSystemConfig('memories.timeline.default_path');
return array_map(
static fn ($path) => self::sanitizePath(trim($path))
?? throw new \InvalidArgumentException("Invalid timeline path: {$path}"),
explode(';', $paths),
);
}
/**
* Sanitize a path to keep only ASCII characters and special characters.
* Null will be returned on error.
*/
public static function sanitizePath(string $path): ?string
{
// remove double slashes and such
$normalized = \OC\Files\Filesystem::normalizePath($path, false);
// look for invalid characters and pattern
if (!\OC\Files\Filesystem::isValidPath($normalized)) {
return null;
}
return $normalized;
}
/**
* Convert SQL UTC date to timestamp.
*/
public static function sqlUtcToTimestamp(string $sqlDate): int
{
try {
return (new \DateTime($sqlDate, new \DateTimeZone('UTC')))->getTimestamp();
} catch (\Throwable) {
return 0;
}
}
/**
* Explode a string into fixed number of components.
*
* @param non-empty-string $delimiter Delimiter
* @param string $string String to explode
* @param int $count Number of components
*
* @return string[] Array of components
*/
public static function explode_exact(string $delimiter, string $string, int $count): array
{
return array_pad(explode($delimiter, $string, $count), $count, '');
}
/**
* Get a system config key with the correct default.
*
* @param string $key System config key
* @param mixed $default Default value
*/
public static function getSystemConfig(string $key, mixed $default = null): mixed
{
$config = \OC::$server->get(\OCP\IConfig::class);
$defaults = self::systemConfigDefaults();
if (!\array_key_exists($key, $defaults)) {
throw new \InvalidArgumentException("Invalid system config key: {$key}");
}
return $config->getSystemValue($key, $default ?? $defaults[$key]);
}
/**
* Set a system config key.
*
* @throws \InvalidArgumentException
*/
public static function setSystemConfig(string $key, mixed $value): void
{
$config = \OC::$server->get(\OCP\IConfig::class);
// Check if the key is valid
$defaults = self::systemConfigDefaults();
if (!\array_key_exists($key, $defaults)) {
throw new \InvalidArgumentException("Invalid system config key: {$key}");
}
// Key belongs to memories namespace
$isAppKey = str_starts_with($key, Application::APPNAME.'.');
// Check if the value has the correct type
if (null !== $value && \gettype($value) !== \gettype($defaults[$key])) {
$expected = \gettype($defaults[$key]);
$got = \gettype($value);
throw new \InvalidArgumentException("Invalid type for system config {$key}, expected {$expected}, got {$got}");
}
// Do not allow null for non-app keys
if (!$isAppKey && null === $value) {
throw new \InvalidArgumentException("Invalid value for system config {$key}, null is not allowed");
}
if ($isAppKey && ($value === $defaults[$key] || null === $value)) {
$config->deleteSystemValue($key);
} else {
$config->setSystemValue($key, $value);
}
}
/** Get list of defaults for all system config keys. */
public static function systemConfigDefaults(): array
{
return require __DIR__.'/SystemConfigDefault.php';
2023-02-06 03:46:44 +00:00
}
/**
* Get the instance ID for this instance.
*/
public static function getInstanceId(): string
{
return self::getSystemConfig('instanceid');
}
/**
* Checks if the API call was made from a native interface.
*/
public static function callerIsNative(): bool
{
// Should not use IRequest here since this method is called during registration
if (\array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER)) {
return 'gallery.memories' === $_SERVER['HTTP_X_REQUESTED_WITH'];
}
return str_contains($_SERVER['HTTP_USER_AGENT'] ?? '', 'MemoriesNative');
}
/**
* Get the version of the native caller.
*/
public static function callerNativeVersion(): ?string
{
$userAgent = \OC::$server->get(\OCP\IRequest::class)->getHeader('User-Agent');
$matches = [];
if (preg_match('/MemoriesNative\/([0-9.]+)/', $userAgent, $matches)) {
return $matches[1];
}
return null;
}
2022-10-19 17:10:36 +00:00
}