nsHelper === null) {
$this->nsHelper = $this->loadHelper('catmenu_namespace');
}
return $this->nsHelper;
}
public function getType() {
return 'substition'; // substitution = remplacer la balise par du contenu
}
public function getPType() {
return 'block';
}
public function getSort() { // priorité du plugin par rapport à d'autres
return 15;
}
/**
* Reconnaît la syntaxe {{catmenu>[namespace]}}
*/
public function connectTo($mode) {
$this->Lexer->addSpecialPattern('{{catmenu>.*?}}', $mode, 'plugin_catmenu_catmenu');
}
/**
* Nettoie {{catmenu>[namespace]}} et extrait le namespace.
*/
public function handle($match, $state, $pos, Doku_Handler $handler) {
$namespace = trim(substr($match, 10, -2)); // retirer {{catmenu> et }}
return ['namespace' => $namespace];
}
public function render($mode, Doku_Renderer $renderer, $data) {
if ($mode !== 'xhtml') return false;
global $ID;
global $conf;
$random = uniqid();
$nsHelper = $this->getNsHelper();
if ($data['namespace'] === '.') { // Résolution du namespace courant
$namespace = $nsHelper->getCurrentNamespace($ID);
} else {
$namespace = cleanID($data['namespace']);
}
$pages = $this->getPagesAndSubfoldersItems($namespace);
if ($pages === false) {
$renderer->doc .= '
' . hsc($this->getLang('namespace_not_found')) . '
';
return true;
}
$renderer->doc .= '';
$renderer->doc .= "";
// Injection du footer DokuCode (si configuré)
$footerContent = trim((string)$this->getConf('footer_content'));
if ($footerContent !== '') {
$footerHtml = p_render('xhtml', p_get_instructions($footerContent), $info);
if ($footerHtml !== '') {
$renderer->doc .= '';
}
}
return true;
}
/**
* Récupère à la fois les pages et les sous-dossiers d'un namespace.
*
* @return array|false Tableau d'items ou false si le namespace n'existe pas
*/
public function getPagesAndSubfoldersItems($namespace) {
global $conf;
$skipPageWithoutTitle = (bool)$this->getConf('skip_page_without_title');
$nsHelper = $this->getNsHelper();
$childrens = @scandir($nsHelper->namespaceDir($namespace));
if ($childrens === false) {
return false;
}
$start = $conf['start']; // page de démarrage (ex. 'accueil', 'start')
$items = [];
foreach ($childrens as $child) {
if ($child[0] === '.') { // ignorer ., .. et fichiers cachés
continue;
}
$childPathInfo = pathinfo($child);
$childID = cleanID($childPathInfo['filename']);
$childNamespace = cleanID($namespace !== '' ? ($namespace . ':' . $childID) : $childID);
$childHasExtension = isset($childPathInfo['extension']) && $childPathInfo['extension'] !== '';
$isDirNamespace = is_dir($nsHelper->namespaceDir($childNamespace));
$isPageNamespace = page_exists($childNamespace);
if (!$childHasExtension && $isDirNamespace) { // Dossier/namespace
$pageNamespaceInfo = $nsHelper->getPageNamespaceInfo($childNamespace);
if ($nsHelper->isHomepage($childID, (string)$pageNamespaceInfo['parentID'])) {
// Aplatir les dossiers "page d'accueil" (ex. ns:ns) — leurs enfants remontent d'un niveau.
$subItems = $this->getPagesAndSubfoldersItems($childNamespace);
if (is_array($subItems) && $subItems) {
$items = array_merge($items, $subItems);
}
continue;
}
$pageID = null;
if (page_exists("$childNamespace:$start")) {
// Page d'accueil standard
$pageID = "$childNamespace:$start";
} elseif (page_exists("$childNamespace:$childID")) {
// Page homonyme dans le dossier
$pageID = "$childNamespace:$childID";
} elseif ($isPageNamespace) {
// Page homonyme au même niveau que le dossier
$pageID = cleanID($namespace !== '' ? ($namespace . ':' . $childID) : $childID);
}
$permission = auth_quickaclcheck($pageID);
if ($permission < AUTH_READ) {
continue;
}
$title = $pageID ? p_get_first_heading($pageID) : $pageID;
if (empty($title)) {
if ($skipPageWithoutTitle || empty($pageID)) {
continue;
}
$title = noNS($pageID);
}
$items[] = [
'title' => $title,
'url' => $pageID ? wl($pageID) : null,
'icon' => $this->getPageImage($pageID),
'pagesiconUploadUrl' => $this->getPagesiconUploadUrl($pageID ?: $childNamespace),
'folderNamespace' => $childNamespace,
'namespace' => $childNamespace,
'subtree' => $this->getPagesAndSubfoldersItems($childNamespace),
'permission' => $permission,
];
continue;
}
if (!$isDirNamespace && $isPageNamespace) { // Page seule
$skipRegex = $this->resolveSkipRegex();
if (!empty($skipRegex) && preg_match($skipRegex, $childNamespace)) {
continue;
}
$pageNamespaceInfo = $nsHelper->getPageNamespaceInfo("$namespace:$childID");
if ($nsHelper->isHomepage($childID, $pageNamespaceInfo['parentID'])) {
continue;
}
$permission = auth_quickaclcheck($childNamespace);
if ($permission < AUTH_READ) {
continue;
}
$title = p_get_first_heading($childNamespace);
if (empty($title)) {
if ($skipPageWithoutTitle) {
continue;
}
$title = noNS($childNamespace);
}
$items[] = [
'title' => $title,
'url' => $childNamespace ? wl($childNamespace) : null,
'icon' => $this->getPageImage($childNamespace),
'pagesiconUploadUrl' => $this->getPagesiconUploadUrl($childNamespace),
'folderNamespace' => $namespace,
'namespace' => $childNamespace,
'permission' => $permission,
];
}
}
return $items;
}
/**
* Résout la valeur effective de l'option skip_file.
*
* Si la valeur est le jeton spécial "@hidepages", retourne la regex de masquage
* de pages configurée globalement dans DokuWiki ($conf['hidepages']).
* Sinon, retourne la valeur brute telle quelle.
*/
private function resolveSkipRegex(): string
{
global $conf;
$raw = (string)$this->getConf('skip_file');
if (trim($raw) === '@hidepages') {
return (string)($conf['hidepages'] ?? '');
}
return $raw;
}
/**
* Retourne l'URL de la miniature d'icône via le helper pagesicon.
* Retourne une chaîne vide si aucune icône n'est définie.
*/
public function getPageImage($page) {
if (!$page) return '';
$page = cleanID((string)$page);
if ($page === '') return '';
/** @var helper_plugin_pagesicon|null $helper */
$helper = plugin_load('helper', 'pagesicon');
if (!$helper) return '';
$namespace = getNS($page);
$pageID = noNS($page);
// Nouvelle API pagesicon (préférée)
if (method_exists($helper, 'getPageIconUrl')) {
$mtime = null;
$iconUrl = $helper->getPageIconUrl($namespace, $pageID, 'smallorbig', ['width' => 55], $mtime, true);
if ($iconUrl) return $iconUrl;
} elseif (method_exists($helper, 'getImageIcon')) {
$mtime = null;
$withDefaultSupported = false;
try {
$method = new ReflectionMethod($helper, 'getImageIcon');
$withDefaultSupported = $method->getNumberOfParameters() >= 6;
} catch (ReflectionException $e) {
$withDefaultSupported = false;
}
$iconUrl = $withDefaultSupported
? $helper->getImageIcon($namespace, $pageID, 'smallorbig', ['width' => 55], $mtime, true)
: $helper->getImageIcon($namespace, $pageID, 'smallorbig', ['width' => 55], $mtime);
if ($iconUrl) return $iconUrl;
}
// Fallback : récupération de l'ID média puis génération de l'URL
$iconMediaID = false;
if (method_exists($helper, 'getPageIconId')) {
$iconMediaID = $helper->getPageIconId($namespace, $pageID, 'smallorbig');
} elseif (method_exists($helper, 'getPageImage')) {
$withDefaultSupported = false;
try {
$method = new ReflectionMethod($helper, 'getPageImage');
$withDefaultSupported = $method->getNumberOfParameters() >= 4;
} catch (ReflectionException $e) {
$withDefaultSupported = false;
}
$iconMediaID = $withDefaultSupported
? $helper->getPageImage($namespace, $pageID, 'smallorbig', true)
: $helper->getPageImage($namespace, $pageID, 'smallorbig');
}
if (!$iconMediaID) return '';
return ml($iconMediaID, ['width' => 55]);
}
/**
* Retourne l'URL de la page d'upload d'icône pour un namespace (via pagesicon).
*/
private function getPagesiconUploadUrl($namespace) {
if ($this->pagesiconHelper === false) {
$this->pagesiconHelper = plugin_load('helper', 'pagesicon');
}
if (!$this->pagesiconHelper) return null;
if (!method_exists($this->pagesiconHelper, 'getUploadIconPage')) return null;
return $this->pagesiconHelper->getUploadIconPage((string)$namespace);
}
}