OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
43
app/Domain/Reactions/Js/reactionsController.js
Normal file
43
app/Domain/Reactions/Js/reactionsController.js
Normal file
@@ -0,0 +1,43 @@
|
||||
leantime.reactionsController = (function () {
|
||||
|
||||
|
||||
//Functions
|
||||
|
||||
let addReactions = function (module, moduleId, reaction, clb) {
|
||||
|
||||
leantime.rpc('Reactions.Reactions.react', {
|
||||
module: module,
|
||||
moduleId: moduleId,
|
||||
reaction: reaction
|
||||
}).then(function (success) {
|
||||
// The service returns false when the reaction was not applied
|
||||
// (e.g. the user already reacted) — only update the UI on success.
|
||||
if (success) {
|
||||
clb();
|
||||
}
|
||||
}).catch(function (e) { console.error('Could not add reaction', e); });
|
||||
|
||||
};
|
||||
|
||||
let removeReaction = function (module, moduleId, reaction, clb) {
|
||||
|
||||
leantime.rpc('Reactions.Reactions.unreact', {
|
||||
module: module,
|
||||
moduleId: moduleId,
|
||||
reaction: reaction
|
||||
}).then(function (success) {
|
||||
// Only update the UI when the reaction was actually removed.
|
||||
if (success) {
|
||||
clb();
|
||||
}
|
||||
}).catch(function (e) { console.error('Could not remove reaction', e); });
|
||||
|
||||
};
|
||||
|
||||
// Make public what you want to have public, everything else is private
|
||||
return {
|
||||
addReactions:addReactions,
|
||||
removeReaction:removeReaction
|
||||
|
||||
};
|
||||
})();
|
||||
77
app/Domain/Reactions/Models/Reactions.php
Normal file
77
app/Domain/Reactions/Models/Reactions.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Reactions\Models;
|
||||
|
||||
use Leantime\Core\Events\DispatchesEvents;
|
||||
|
||||
class Reactions
|
||||
{
|
||||
use DispatchesEvents;
|
||||
|
||||
/**
|
||||
* @var array list of available reactions by reaction type
|
||||
*/
|
||||
private static array $reactionTypes = [
|
||||
'sentimentReactions' => [
|
||||
'like' => '👍',
|
||||
'anger' => '😡',
|
||||
'love' => '❤',
|
||||
'support' => '💯',
|
||||
'celebrate' => '🎉',
|
||||
'interesting' => '💡',
|
||||
'sad' => '😥',
|
||||
'funny' => '😂',
|
||||
],
|
||||
'contentReactions' => [
|
||||
'upvote' => "<i class='fa-solid fa-up'></i>",
|
||||
'downvote' => "<i class='fa-solid fa-down'></i>",
|
||||
],
|
||||
'entityReactions' => [
|
||||
'favorite' => "<i class='fa fa-star'></i>",
|
||||
'watch' => "<i class='fa fa-eye'></i>",
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
public static string $favorite = 'favorite';
|
||||
|
||||
public static string $watch = 'watch';
|
||||
|
||||
public static string $downvote = 'downvote';
|
||||
|
||||
public static string $upvote = 'upvote';
|
||||
|
||||
public static string $funny = 'funny';
|
||||
|
||||
public static string $like = 'like';
|
||||
|
||||
public static string $anger = 'anger';
|
||||
|
||||
public static string $love = 'love';
|
||||
|
||||
public static string $support = 'support';
|
||||
|
||||
public static string $celebrate = 'celebrate';
|
||||
|
||||
public static string $interesting = 'interesting';
|
||||
|
||||
public static string $sad = 'sad';
|
||||
|
||||
public static function getReactions(): mixed
|
||||
{
|
||||
return self::dispatch_filter('available_reactions', self::$reactionTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false|mixed
|
||||
*/
|
||||
/**
|
||||
* @return false|mixed
|
||||
*/
|
||||
public static function getReactionsByType(string $type): mixed
|
||||
{
|
||||
$reactions = self::dispatch_filter('available_reactions', self::$reactionTypes);
|
||||
|
||||
return $reactions[$type] ?? false;
|
||||
}
|
||||
}
|
||||
155
app/Domain/Reactions/Repositories/Reactions.php
Normal file
155
app/Domain/Reactions/Repositories/Reactions.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Reactions\Repositories;
|
||||
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Leantime\Core\Db\DatabaseHelper;
|
||||
use Leantime\Core\Db\Db as DbCore;
|
||||
|
||||
/**
|
||||
* Repository for managing user reactions on entities.
|
||||
*/
|
||||
class Reactions
|
||||
{
|
||||
private ConnectionInterface $db;
|
||||
|
||||
private DatabaseHelper $dbHelper;
|
||||
|
||||
public function __construct(DbCore $db, DatabaseHelper $dbHelper)
|
||||
{
|
||||
$this->db = $db->getConnection();
|
||||
$this->dbHelper = $dbHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a reaction to an entity.
|
||||
*/
|
||||
public function addReaction(int $userId, string $module, int $moduleId, string $reaction): bool
|
||||
{
|
||||
return $this->db->table('zp_reactions')->insert([
|
||||
'module' => $module,
|
||||
'moduleId' => $moduleId,
|
||||
'userId' => $userId,
|
||||
'reaction' => $reaction,
|
||||
'date' => dtHelper()->userNow()->formatDateTimeForDb(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* getGroupedEntityReactions - gets all reactions for a given entity grouped and counted by reactions
|
||||
*
|
||||
* @return array|false returns the array on success or false on failure
|
||||
*/
|
||||
public function getGroupedEntityReactions(string $module, int $moduleId): array|false
|
||||
{
|
||||
$results = $this->db->table('zp_reactions')
|
||||
->selectRaw('COUNT(reaction) AS '.$this->dbHelper->wrapColumn('reactionCount').', reaction')
|
||||
->where('module', $module)
|
||||
->where('moduleId', $moduleId)
|
||||
->groupBy('reaction')
|
||||
->get();
|
||||
|
||||
return array_map(fn ($item) => (array) $item, $results->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* getMyReactions - gets user reactions. Can be very broad or very targeted
|
||||
*/
|
||||
public function getUserReactions(int $userId, string $module = '', ?int $moduleId = null, string $reaction = ''): array|false
|
||||
{
|
||||
$query = $this->db->table('zp_reactions')
|
||||
->select('id', 'reaction', 'date', 'module', 'moduleId', 'userId')
|
||||
->where('userId', $userId);
|
||||
|
||||
if ($module !== '') {
|
||||
$query->where('module', $module);
|
||||
}
|
||||
if ($moduleId !== null) {
|
||||
$query->where('moduleId', $moduleId);
|
||||
}
|
||||
if ($reaction !== '') {
|
||||
$query->where('reaction', $reaction);
|
||||
}
|
||||
|
||||
$results = $query->get();
|
||||
|
||||
return array_map(fn ($item) => (array) $item, $results->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* removeReactionById - removes a reaction by reaction id
|
||||
*/
|
||||
public function removeReactionById(int $id): bool
|
||||
{
|
||||
return $this->db->table('zp_reactions')
|
||||
->where('id', $id)
|
||||
->delete() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* removeUserReaction - removes a users reaction to an entity
|
||||
*/
|
||||
public function removeUserReaction(int $userId, string $module, int $moduleId, string $reaction): bool
|
||||
{
|
||||
return $this->db->table('zp_reactions')
|
||||
->where('module', $module)
|
||||
->where('moduleId', $moduleId)
|
||||
->where('userId', $userId)
|
||||
->where('reaction', $reaction)
|
||||
->delete() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* getReactionsByModule - gets reactions count by module
|
||||
*/
|
||||
public function getReactionsByModule(string $module, ?int $moduleId = null): array|false
|
||||
{
|
||||
$query = $this->db->table('zp_reactions')
|
||||
->selectRaw('reaction, COUNT(reaction) as '.$this->dbHelper->wrapColumn('reactionCount'))
|
||||
->where('module', $module);
|
||||
|
||||
if ($moduleId !== null) {
|
||||
$query->where('moduleId', $moduleId);
|
||||
}
|
||||
|
||||
$results = $query->groupBy('reaction')
|
||||
->get();
|
||||
|
||||
return array_map(fn ($item) => (array) $item, $results->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* getEntityReactionsWithUsers - gets all reactions for an entity with user names
|
||||
*
|
||||
* @return array returns array grouped by reaction with user names
|
||||
*/
|
||||
public function getEntityReactionsWithUsers(string $module, int $moduleId): array
|
||||
{
|
||||
$results = $this->db->table('zp_reactions')
|
||||
->select('zp_reactions.reaction', 'zp_reactions.userId', 'zp_user.firstname', 'zp_user.lastname')
|
||||
->leftJoin('zp_user', 'zp_reactions.userId', '=', 'zp_user.id')
|
||||
->where('zp_reactions.module', $module)
|
||||
->where('zp_reactions.moduleId', $moduleId)
|
||||
->get();
|
||||
|
||||
// Group by reaction and collect user names
|
||||
$grouped = [];
|
||||
foreach ($results as $row) {
|
||||
$reaction = $row->reaction;
|
||||
if (! isset($grouped[$reaction])) {
|
||||
$grouped[$reaction] = [
|
||||
'reaction' => $reaction,
|
||||
'reactionCount' => 0,
|
||||
'users' => [],
|
||||
];
|
||||
}
|
||||
$grouped[$reaction]['reactionCount']++;
|
||||
$grouped[$reaction]['users'][] = [
|
||||
'userId' => $row->userId,
|
||||
'name' => trim(($row->firstname ?? '').' '.($row->lastname ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
return array_values($grouped);
|
||||
}
|
||||
}
|
||||
153
app/Domain/Reactions/Services/Reactions.php
Normal file
153
app/Domain/Reactions/Services/Reactions.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Leantime\Domain\Reactions\Services;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
class Reactions
|
||||
{
|
||||
/**
|
||||
* @var \Leantime\Domain\Reactions\Repositories\Reactions reactions repository
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
private \Leantime\Domain\Reactions\Repositories\Reactions $reactionsRepo;
|
||||
|
||||
public function __construct(\Leantime\Domain\Reactions\Repositories\Reactions $reactionsRepo)
|
||||
{
|
||||
$this->reactionsRepo = $reactionsRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a reaction on behalf of the CURRENT (session) user.
|
||||
*
|
||||
* JSON-RPC entry point: derives the user from the session rather than
|
||||
* accepting a userId, so a caller cannot react as another user.
|
||||
*
|
||||
* @param string $module The entity module (e.g. 'tickets')
|
||||
* @param int $moduleId The entity id
|
||||
* @param string $reaction The reaction key
|
||||
* @return bool True if the reaction was added
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function react(string $module, int $moduleId, string $reaction): bool
|
||||
{
|
||||
return $this->addReaction((int) session('userdata.id'), $module, $moduleId, $reaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a reaction on behalf of the CURRENT (session) user.
|
||||
*
|
||||
* JSON-RPC entry point: derives the user from the session rather than
|
||||
* accepting a userId, so a caller cannot remove another user's reaction.
|
||||
*
|
||||
* @param string $module The entity module (e.g. 'tickets')
|
||||
* @param int $moduleId The entity id
|
||||
* @param string $reaction The reaction key
|
||||
* @return bool True if the reaction was removed
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function unreact(string $module, int $moduleId, string $reaction): bool
|
||||
{
|
||||
return $this->removeReaction((int) session('userdata.id'), $module, $moduleId, $reaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* addReaction - adds a reaction to an entity, checks if a user has already reacted the same way
|
||||
*
|
||||
* Not exposed via JSON-RPC: it accepts an arbitrary $userId. Use the
|
||||
* session-scoped react() wrapper for JSON-RPC callers.
|
||||
*/
|
||||
public function addReaction(int $userId, string $module, int $moduleId, string $reaction): bool
|
||||
{
|
||||
if ($module == '' || $moduleId == '' || $userId == '' || $reaction == '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if user already reacted in that category
|
||||
$userReactions = $this->getUserReactions($userId, $module, $moduleId);
|
||||
|
||||
$currentReactionType = $this->getReactionType($reaction);
|
||||
|
||||
foreach ($userReactions as $previousReaction) {
|
||||
if ($this->getReactionType($previousReaction['reaction']) == $currentReactionType) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->reactionsRepo->addReaction($userId, $module, $moduleId, $reaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* getReactionType - returns the category/type of a given reaction
|
||||
*
|
||||
*
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getReactionType(string $reaction): string|false
|
||||
{
|
||||
|
||||
$types = \Leantime\Domain\Reactions\Models\Reactions::getReactions();
|
||||
|
||||
foreach ($types as $reactionType => $reactionValues) {
|
||||
if (isset($reactionValues[$reaction])) {
|
||||
return $reactionType;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* getGroupedEntityReactions - gets all reactions for a given entity grouped and counted by reactions
|
||||
*
|
||||
*
|
||||
* @return array|false returns the array on success or false on failure
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getGroupedEntityReactions(string $module, int $moduleId): array|false
|
||||
{
|
||||
return $this->reactionsRepo->getGroupedEntityReactions($module, $moduleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* getMyReactions - gets user reactions. Can be very broad or very targeted
|
||||
*
|
||||
*
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getUserReactions(int $userId, string $module = '', ?int $moduleId = null, string $reaction = ''): array|false
|
||||
{
|
||||
|
||||
return $this->reactionsRepo->getUserReactions($userId, $module, $moduleId, $reaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* removeReaction - removes a user's reaction from an entity
|
||||
*
|
||||
* Not exposed via JSON-RPC (accepts an arbitrary $userId). Use the
|
||||
* session-scoped unreact() wrapper for JSON-RPC callers.
|
||||
*/
|
||||
public function removeReaction(int $userId, string $module, int $moduleId, string $reaction): bool
|
||||
{
|
||||
return $this->reactionsRepo->removeUserReaction($userId, $module, $moduleId, $reaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* getEntityReactionsWithUsers - gets all reactions for an entity with user names
|
||||
*
|
||||
* @return array returns array grouped by reaction with user info
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getEntityReactionsWithUsers(string $module, int $moduleId): array
|
||||
{
|
||||
return $this->reactionsRepo->getEntityReactionsWithUsers($module, $moduleId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user