Elgg 是一個開源社交網路引擎,其實體系統提供靈活的圖示處理機制。本文深入解析 Elgg 實體圖示的儲存流程、檔案命名規則、目錄結構,以及如何透過 Plugin Hook 自定義圖示 URL,適用於 Elgg 2.x/3.x/4.x 開發者。
當實體(如 Activity 類別繼承 ElggObject)建立時,若上傳了圖示檔案,系統會執行以下流程:
<?php
// 典型儲存邏輯(簡化自原始碼)
if (isset($_FILES['icon']) && substr_count($_FILES['icon']['type'], 'image/')) {
$prefix = "activity/{$activity->guid}";
$filehandler = new ElggFile();
$filehandler->owner_guid = $activity->owner_guid;
$filehandler->setFilename($prefix . ".jpg");
$filehandler->open("write");
$filehandler->write(get_uploaded_file('icon'));
$filehandler->close();
// 生成多種尺寸縮圖
$sizes = [
'tiny' => [25, 25, true],
'small' => [40, 40, true],
'medium' => [100, 100, true],
'large' => [200, 200, false],
];
foreach ($sizes as $suffix => [$w, $h, $crop]) {
$resized = get_resized_image_from_existing_file(
$filehandler->getFilenameOnFilestore(), $w, $h, $crop
);
if ($resized) {
$thumb = new ElggFile();
$thumb->owner_guid = $activity->owner_guid;
$thumb->setMimeType('image/jpeg');
$thumb->setFilename("{$prefix}{$suffix}.jpg");
$thumb->open("write");
$thumb->write($resized);
$thumb->close();
}
}
}
?>
Elgg 使用雜湊分佈目錄避免單一目錄檔案過多:
// 使用者 GUID 為 123456 時
// 目錄結構:{dataroot}/123/45/6/
// 檔案命名:{prefix}{size}.jpg
// 例如:activity/789tiny.jpg → 實際路徑
// {dataroot}/123/45/6/activity/789tiny.jpg
ElggFile::getFilenameOnFilestore() 返回完整檔案系統路徑,供 GD/Imagick 處理。
Elgg 核心不直接產生圖示 URL,而是透過事件觸發機制:
<?php
// Elgg 核心 entities.php 簡化邏輯
public function getIcon($size = 'medium') {
// ...
$url = $this->getIconURL($size);
return $url;
}
public function getIconURL($size = 'medium', $params = []) {
$url = $this->getDefaultIconURL($size); // 預設 URL
// 觸發鉤子允許外掛修改 URL
$url = elgg_trigger_plugin_hook('entity:icon:url', $this->getType(), [
'entity' => $this,
'size' => $size,
'params' => $params,
], $url);
return $url;
}
?>
start.php 註冊鉤子<?php
// mod/your_plugin/start.php
elgg_register_plugin_hook_handler('entity:icon:url', 'object', 'your_plugin_activity_icon_url');
function your_plugin_activity_icon_url($hook, $type, $returnvalue, $params) {
$entity = $params['entity'];
$size = $params['size'];
// 僅處理 Activity 子類型
if ($entity->getSubtype() !== 'activity') {
return $returnvalue;
}
// 自定義邏輯:例如從 CDN 取圖、加簽名 URL、特定尺寸重導向
$guid = $entity->guid;
$cdnBase = 'https://cdn.example.com/activity-icons/';
// 檢查檔案是否存在(可選,視效能需求)
// $exists = check_cdn_file_exists("{$cdnBase}{$guid}/{$size}.jpg");
return "{$cdnBase}{$guid}/{$size}.jpg";
}
?>
<?php
function your_plugin_activity_icon_url($hook, $type, $url, $params) {
$entity = $params['entity'];
$size = $params['size'];
if ($entity->getSubtype() !== 'activity') return $url;
// 範例:草稿狀態顯示預設圖示,發布後顯示真實圖示
if ($entity->status === 'draft') {
return elgg_get_simplecache_url('graphics/default_activity_draft.png');
}
// 範例:私有活動加簽名 URL 防止熱連結
if ($entity->access_id === ACCESS_PRIVATE) {
return your_plugin_sign_url("{$url}?t=" . time());
}
return $url;
}
?>
ElggEntity::getIconURL() 覆寫(不推薦)若需求極度特殊,可在實體類別覆寫方法,但會失去鉤子擴充性:
<?php
class Activity extends ElggObject {
public function getIconURL($size = 'medium', $params = []) {
// 完全自定義邏輯,不觸發核心鉤子
return "https://my-cdn.com/activities/{$this->guid}/{$size}.webp";
}
}
?>
| 問題 | 原因 | 解決 |
|---|---|---|
| 圖示不顯示/404 | 檔案未正確寫入資料目錄 | 檢查 $CONFIG->dataroot 權限、ElggFile::open() 回傳值 |
| 縮圖未生成 | GD/Imagick 未安裝或記憶體不足 | 安裝 php-gd 或 php-imagick,調整 memory_limit |
| 鉤子不生效 | 優先級/註冊順序問題 | 使用 elgg_register_plugin_hook_handler(..., $priority) 調整優先級(預設 500) |
| 快取導致舊圖示 | SimpleCache/瀏覽器快取 | 更新實體時呼叫 $entity->invalidateCache(),或 URL 加版本參數 |
$_SERVER['HTTP_ACCEPT'] 回傳最佳格式。elgg_register_plugin_hook_handler(),鉤子名稱 entity:icon:url。$services->hooks->registerHandler('entity:icon:url', 'object', $callable)。EventDispatcher 介面。Elgg 實體圖示系統採用「儲存時生成多尺寸」+「讀取時透過 Hook 動態決定 URL」的解耦設計。開發者應善用 entity:icon:url 鉤子實現 CDN 整合、權限控制、格式協商等進階需求,而非直接修改核心或覆寫實體方法。理解檔案命名規則({type}/{guid}{size}.jpg)與資料目錄分佈邏輯,有助於除錯與遷移。