Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
}
],
"require": {
"php": "^8.1",
"ramsey/uuid": "^4.9"
"php": "^8.1"
},
"require-dev": {
"cyclonedx/cyclonedx-php-composer": "^6.2",
Expand Down
602 changes: 207 additions & 395 deletions composer.lock

Large diffs are not rendered by default.

18 changes: 6 additions & 12 deletions lib/Db/AdminSetting.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

namespace OCA\MyDash\Db;

use DateTime;
use JsonSerializable;
use OCP\AppFramework\Db\Entity;

Expand All @@ -32,8 +31,8 @@
* @method void setSettingKey(string $settingKey)
* @method string|null getSettingValue()
* @method void setSettingValue(?string $settingValue)
* @method DateTime getUpdatedAt()
* @method void setUpdatedAt(DateTime $updatedAt)
* @method string|null getUpdatedAt()
* @method void setUpdatedAt(?string $updatedAt)
*/
class AdminSetting extends Entity implements JsonSerializable
{
Expand Down Expand Up @@ -89,11 +88,11 @@ class AdminSetting extends Entity implements JsonSerializable
protected ?string $settingValue = null;

/**
* The update timestamp.
* The update timestamp (ISO-8601 / 'c' format).
*
* @var DateTime|null
* @var string|null
*/
protected ?DateTime $updatedAt = null;
protected ?string $updatedAt = null;

/**
* Constructor
Expand Down Expand Up @@ -145,16 +144,11 @@ public function setValueEncoded(mixed $value): void
*/
public function jsonSerialize(): array
{
$updatedAtValue = null;
if ($this->updatedAt !== null) {
$updatedAtValue = $this->updatedAt->format(format: 'c');
}

return [
'id' => $this->getId(),
'key' => $this->settingKey,
'value' => $this->getValueDecoded(),
'updatedAt' => $updatedAtValue,
'updatedAt' => $this->updatedAt,
];
}//end jsonSerialize()
}//end class
18 changes: 6 additions & 12 deletions lib/Db/ConditionalRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

namespace OCA\MyDash\Db;

use DateTime;
use JsonSerializable;
use OCP\AppFramework\Db\Entity;

Expand All @@ -36,8 +35,8 @@
* @method void setRuleConfig(?string $ruleConfig)
* @method bool getIsInclude()
* @method void setIsInclude(bool $isInclude)
* @method DateTime getCreatedAt()
* @method void setCreatedAt(DateTime $createdAt)
* @method string|null getCreatedAt()
* @method void setCreatedAt(?string $createdAt)
*/
class ConditionalRule extends Entity implements JsonSerializable
{
Expand Down Expand Up @@ -99,11 +98,11 @@ class ConditionalRule extends Entity implements JsonSerializable
protected bool $isInclude = true;

/**
* The creation timestamp.
* The creation timestamp (ISO-8601 / 'c' format).
*
* @var DateTime|null
* @var string|null
*/
protected ?DateTime $createdAt = null;
protected ?string $createdAt = null;

/**
* Constructor
Expand Down Expand Up @@ -163,18 +162,13 @@ public function setRuleConfigArray(array $config): void
*/
public function jsonSerialize(): array
{
$createdAtValue = null;
if ($this->createdAt !== null) {
$createdAtValue = $this->createdAt->format(format: 'c');
}

return [
'id' => $this->getId(),
'widgetPlacementId' => $this->widgetPlacementId,
'ruleType' => $this->ruleType,
'ruleConfig' => $this->getRuleConfigArray(),
'isInclude' => $this->isInclude,
'createdAt' => $createdAtValue,
'createdAt' => $this->createdAt,
];
}//end jsonSerialize()
}//end class
2 changes: 1 addition & 1 deletion lib/Service/ConditionalService.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public function addRule(
$rule->setRuleType($ruleType);
$rule->setRuleConfigArray($ruleConfig);
$rule->setIsInclude($isInclude);
$rule->setCreatedAt(new DateTime());
$rule->setCreatedAt((new DateTime())->format(format: 'c'));

return $this->ruleMapper->insert(entity: $rule);
}//end addRule()
Expand Down
29 changes: 23 additions & 6 deletions lib/Service/TemplateService.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\IGroupManager;
use OCP\IUserManager;
use Ramsey\Uuid\Uuid;

/**
* Service for managing admin dashboard templates.
Expand Down Expand Up @@ -139,8 +138,9 @@ private function buildDashboardFromTemplate(
string $userId,
Dashboard $template
): Dashboard {
$now = (new DateTime())->format(format: 'Y-m-d H:i:s');
$dashboard = new Dashboard();
$dashboard->setUuid(Uuid::uuid4()->toString());
$dashboard->setUuid($this->generateUuid());
$dashboard->setName($template->getName());
$dashboard->setDescription(
$template->getDescription()
Expand All @@ -157,12 +157,28 @@ private function buildDashboardFromTemplate(
$template->getPermissionLevel()
);
$dashboard->setIsActive(true);
$dashboard->setCreatedAt(new DateTime());
$dashboard->setUpdatedAt(new DateTime());
$dashboard->setCreatedAt($now);
$dashboard->setUpdatedAt($now);

return $dashboard;
}//end buildDashboardFromTemplate()

/**
* Generate a v4 UUID using random_bytes (no external dependency).
*
* @return string A v4 UUID.
*/
private function generateUuid(): string
{
$data = random_bytes(length: 16);
$data[6] = chr((ord($data[6]) & 0x0F) | 0x40);
$data[8] = chr((ord($data[8]) & 0x3F) | 0x80);
return vsprintf(
format: '%s%s-%s-%s-%s-%s%s%s',
values: str_split(string: bin2hex(string: $data), length: 4)
);
}//end generateUuid()

/**
* Copy widget placements from a template to a new dashboard.
*
Expand Down Expand Up @@ -221,8 +237,9 @@ private function clonePlacement(
);
$placement->setShowTitle($source->getShowTitle());
$placement->setSortOrder($source->getSortOrder());
$placement->setCreatedAt(new DateTime());
$placement->setUpdatedAt(new DateTime());
$now = (new DateTime())->format(format: 'Y-m-d H:i:s');
$placement->setCreatedAt($now);
$placement->setUpdatedAt($now);

return $placement;
}//end clonePlacement()
Expand Down
10 changes: 9 additions & 1 deletion lib/Service/UserAttributeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

namespace OCA\MyDash\Service;

use OCP\IConfig;
use OCP\IUserManager;

/**
Expand All @@ -32,9 +33,11 @@ class UserAttributeResolver
* Constructor
*
* @param IUserManager $userManager The user manager interface.
* @param IConfig $config Config service for per-user prefs (e.g. language).
*/
public function __construct(
private readonly IUserManager $userManager,
private readonly IConfig $config,
) {
}//end __construct()

Expand All @@ -56,7 +59,12 @@ public function getUserAttributeValue(
}

return match ($attribute) {
'locale' => $user->getLanguage() ?? 'en',
'locale' => $this->config->getUserValue(
userId: $userId,
appName: 'core',
key: 'lang',
default: 'en'
),
'email' => $user->getEMailAddress(),
'displayName' => $user->getDisplayName(),
'quota' => (string) $user->getQuota(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ public function process(File $phpcsFile, $stackPtr): void
return;
}

// Skip Entity magic setter/getter calls. Nextcloud's OCP\AppFramework\Db\Entity
// routes set*/get*/is* through __call which uses $args[0] and breaks with named
// parameters. Files extending Entity must use positional args for these.
if ($this->isEntityMagicAccessor(phpcsFile: $phpcsFile, stackPtr: $stackPtr) === true) {
return;
}

// Get the closing parenthesis.
if (isset($tokens[$openParen]['parenthesis_closer']) === false) {
return;
Expand Down Expand Up @@ -97,6 +104,116 @@ public function process(File $phpcsFile, $stackPtr): void
}//end process()


/**
* Check whether the call at $stackPtr is an Entity magic accessor (setX/getX/isX)
* inside a class that extends OCP\AppFramework\Db\Entity.
*
* @param File $phpcsFile The file being scanned.
* @param int $stackPtr Position of the method-name T_STRING.
*
* @return bool True if this is a magic Entity accessor call.
*/
private function isEntityMagicAccessor(File $phpcsFile, int $stackPtr): bool
{
$tokens = $phpcsFile->getTokens();
$name = $tokens[$stackPtr]['content'];

// Only setX/getX/isX names with at least one trailing capital qualify.
if (preg_match(pattern: '/^(set|get|is)[A-Z]/', subject: $name) !== 1) {
return false;
}

// Must be a $this->name() call (already established by isInternalCall, but
// re-check defensively because we also want to ignore self::set*() etc.).
$prev = $phpcsFile->findPrevious(
types: [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT],
start: ($stackPtr - 1),
end: null,
exclude: true
);
if ($prev === false || $tokens[$prev]['code'] !== T_OBJECT_OPERATOR) {
return false;
}

return $this->classExtendsEntity(phpcsFile: $phpcsFile);

}//end isEntityMagicAccessor()


/**
* Check if the file's primary class extends OCP\AppFramework\Db\Entity.
*
* Considers both fully-qualified and use-aliased "Entity" parents.
*
* @param File $phpcsFile The file being scanned.
*
* @return bool True when the class extends Entity.
*/
private function classExtendsEntity(File $phpcsFile): bool
{
$tokens = $phpcsFile->getTokens();
$classPtr = $phpcsFile->findNext(types: T_CLASS, start: 0);
if ($classPtr === false) {
return false;
}

$extendsPtr = $phpcsFile->findNext(
types: T_EXTENDS,
start: $classPtr,
end: ($tokens[$classPtr]['scope_opener'] ?? $phpcsFile->numTokens)
);
if ($extendsPtr === false) {
return false;
}

// Read the parent class identifier (handles namespaced parents like \OCP\…\Entity).
$parentName = '';
$j = ($extendsPtr + 1);
$end = ($tokens[$classPtr]['scope_opener'] ?? $phpcsFile->numTokens);
while ($j < $end) {
$code = $tokens[$j]['code'];
if ($code === T_OPEN_CURLY_BRACKET || $code === T_IMPLEMENTS || $code === T_COMMA) {
break;
}

if ($code !== T_WHITESPACE && $code !== T_COMMENT && $code !== T_DOC_COMMENT) {
$parentName .= $tokens[$j]['content'];
}

$j++;
}

$parentName = trim(string: $parentName);
if ($parentName === '') {
return false;
}

// Direct fully-qualified match.
if ($parentName === '\OCP\AppFramework\Db\Entity'
|| $parentName === 'OCP\AppFramework\Db\Entity'
) {
return true;
}

// Aliased: parent is "Entity" and a use statement imports it from the OCP path.
if ($parentName === 'Entity') {
for ($i = 0; $i < $classPtr; $i++) {
if ($tokens[$i]['code'] !== T_USE) {
continue;
}

$usePath = $this->getUseStatementPath(phpcsFile: $phpcsFile, usePtr: $i);
if ($usePath === 'OCP\AppFramework\Db\Entity') {
return true;
}
}
}

return false;

}//end classExtendsEntity()


/**
* Check if the T_STRING at $stackPtr is part of a function/method definition (not a call).
*
Expand Down
2 changes: 2 additions & 0 deletions src/components/DashboardSwitcher.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
<NcSelect
v-model="selectedDashboard"
:options="dashboardOptions"
:input-label="t('mydash', 'Active dashboard')"
:placeholder="t('mydash', 'Select dashboard')"
label="label"
track-by="id"
class="dashboard-switcher"
hide-label
@input="switchDashboard" />
</template>

Expand Down
11 changes: 8 additions & 3 deletions src/components/TileEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
<NcSelect
v-model="selectedIcon"
:options="iconOptions"
:label="t('mydash', 'Icon')"
:input-label="t('mydash', 'Icon')"
label="label"
label-outside>
<template #selected-option="{ label }">
<div class="icon-option">
Expand Down Expand Up @@ -72,7 +73,9 @@
<NcColorPicker
:value.sync="form.backgroundColor"
@input="form.backgroundColor = $event">
<NcButton type="tertiary">
<NcButton
type="tertiary"
:aria-label="t('mydash', 'Pick background color')">
<template #icon>
<div
class="color-preview"
Expand All @@ -88,7 +91,9 @@
<NcColorPicker
:value.sync="form.textColor"
@input="form.textColor = $event">
<NcButton type="tertiary">
<NcButton
type="tertiary"
:aria-label="t('mydash', 'Pick text color')">
<template #icon>
<div
class="color-preview"
Expand Down
Loading
Loading