Upload files to "/"
This commit is contained in:
@@ -0,0 +1,319 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file plugins/generic/cleanUserUrls/CleanUserUrlsPlugin.php
|
||||||
|
*
|
||||||
|
* A small site-wide OJS 3.5 plugin that replaces the site-level account menu
|
||||||
|
* URLs with clean aliases. Apache/Nginx must map those aliases back to OJS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace APP\plugins\generic\cleanUserUrls;
|
||||||
|
|
||||||
|
use APP\core\Application;
|
||||||
|
use PKP\navigationMenu\NavigationMenuItem;
|
||||||
|
use PKP\plugins\GenericPlugin;
|
||||||
|
use PKP\plugins\Hook;
|
||||||
|
use PKP\security\Validation;
|
||||||
|
use PKP\template\PKPTemplateManager;
|
||||||
|
|
||||||
|
class CleanUserUrlsPlugin extends GenericPlugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register hooks when the plugin is enabled.
|
||||||
|
*
|
||||||
|
* @param string $category
|
||||||
|
* @param string $path
|
||||||
|
* @param mixed $mainContextId
|
||||||
|
*/
|
||||||
|
public function register($category, $path, $mainContextId = null)
|
||||||
|
{
|
||||||
|
$success = parent::register($category, $path, $mainContextId);
|
||||||
|
|
||||||
|
if ($success && $this->getEnabled($mainContextId)) {
|
||||||
|
Hook::add('NavigationMenus::displaySettings', $this->rewriteAccountMenuUrl(...));
|
||||||
|
Hook::add('TemplateManager::display', $this->registerOutputFilter(...));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDisplayName()
|
||||||
|
{
|
||||||
|
return __('plugins.generic.cleanUserUrls.displayName');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription()
|
||||||
|
{
|
||||||
|
return __('plugins.generic.cleanUserUrls.description');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This plugin is intended to be enabled from Site Settings and affect only
|
||||||
|
* the site-wide context. Journal-context navigation is left untouched.
|
||||||
|
*/
|
||||||
|
public function isSitePlugin(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register one conservative output filter for site-level pages.
|
||||||
|
*
|
||||||
|
* It only changes:
|
||||||
|
* 1. the public site-name link /index/<locale>/index -> /
|
||||||
|
* 2. the exact Administration breadcrumb link /index/id/admin[/index] -> /admin
|
||||||
|
*
|
||||||
|
* Child admin routes such as /index/id/admin/settings are deliberately
|
||||||
|
* left untouched.
|
||||||
|
*/
|
||||||
|
public function registerOutputFilter(string $hookName, array $args): bool
|
||||||
|
{
|
||||||
|
$templateMgr = $args[0] ?? null;
|
||||||
|
$request = Application::get()->getRequest();
|
||||||
|
|
||||||
|
if (!$templateMgr instanceof PKPTemplateManager || $request->getContext()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$templateMgr->registerFilter('output', $this->rewriteSafeSiteLinks(...));
|
||||||
|
|
||||||
|
// The backend user menu is rendered by Vue after the server-side
|
||||||
|
// Smarty output filter has finished. Add a tiny, site-level backend
|
||||||
|
// helper that updates only the generated Edit Profile and Log Out links.
|
||||||
|
$baseUrl = rtrim($request->getBaseUrl(), '/');
|
||||||
|
|
||||||
|
$legacyProfileUrl = $baseUrl . '/index/id/user/profile';
|
||||||
|
$cleanProfileUrl = $baseUrl . '/user/profile';
|
||||||
|
|
||||||
|
$legacyLogoutUrl = $baseUrl . '/index/id/login/signOut';
|
||||||
|
$cleanLogoutUrl = $baseUrl . '/logout';
|
||||||
|
|
||||||
|
$script = <<<'JS'
|
||||||
|
(function () {
|
||||||
|
const replacements = new Map([
|
||||||
|
[__LEGACY_PROFILE_URL__, __CLEAN_PROFILE_URL__],
|
||||||
|
[__LEGACY_LOGOUT_URL__, __CLEAN_LOGOUT_URL__],
|
||||||
|
]);
|
||||||
|
|
||||||
|
function cleanBackendUserLinks(root) {
|
||||||
|
const scope = root && root.querySelectorAll ? root : document;
|
||||||
|
|
||||||
|
scope.querySelectorAll('a[href]').forEach(function (link) {
|
||||||
|
const replacement = replacements.get(link.href);
|
||||||
|
if (replacement) {
|
||||||
|
link.href = replacement;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
cleanBackendUserLinks(document);
|
||||||
|
|
||||||
|
const observer = new MutationObserver(function (mutations) {
|
||||||
|
mutations.forEach(function (mutation) {
|
||||||
|
mutation.addedNodes.forEach(function (node) {
|
||||||
|
if (node.nodeType !== Node.ELEMENT_NODE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.matches && node.matches('a[href]')) {
|
||||||
|
const replacement = replacements.get(node.href);
|
||||||
|
if (replacement) {
|
||||||
|
node.href = replacement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanBackendUserLinks(node);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', start, { once: true });
|
||||||
|
} else {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
JS;
|
||||||
|
|
||||||
|
$script = str_replace(
|
||||||
|
[
|
||||||
|
'__LEGACY_PROFILE_URL__',
|
||||||
|
'__CLEAN_PROFILE_URL__',
|
||||||
|
'__LEGACY_LOGOUT_URL__',
|
||||||
|
'__CLEAN_LOGOUT_URL__',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
json_encode($legacyProfileUrl),
|
||||||
|
json_encode($cleanProfileUrl),
|
||||||
|
json_encode($legacyLogoutUrl),
|
||||||
|
json_encode($cleanLogoutUrl),
|
||||||
|
],
|
||||||
|
$script
|
||||||
|
);
|
||||||
|
|
||||||
|
$templateMgr->addJavaScript(
|
||||||
|
'cleanUserUrlsBackendUserMenu',
|
||||||
|
$script,
|
||||||
|
[
|
||||||
|
'contexts' => ['backend'],
|
||||||
|
'inline' => true,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewrite known-safe site-level links.
|
||||||
|
*
|
||||||
|
* Normal site-level Administration page URLs are cleaned:
|
||||||
|
* /index/id/admin -> /admin
|
||||||
|
* /index/id/admin/index -> /admin
|
||||||
|
* /index/id/admin/contexts -> /admin/contexts
|
||||||
|
* /index/id/admin/settings -> /admin/settings
|
||||||
|
* /index/id/admin/systemInfo -> /admin/systemInfo
|
||||||
|
* /index/id/admin/jobs -> /admin/jobs
|
||||||
|
* /index/id/admin/failedJobs -> /admin/failedJobs
|
||||||
|
*
|
||||||
|
* AJAX/component ($$$call$$$), API, journal-context and other sensitive
|
||||||
|
* routes are deliberately left untouched.
|
||||||
|
*/
|
||||||
|
public function rewriteSafeSiteLinks(string $output, $template): string
|
||||||
|
{
|
||||||
|
$request = Application::get()->getRequest();
|
||||||
|
|
||||||
|
if ($request->getContext()) {
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
$baseUrl = rtrim($request->getBaseUrl(), '/');
|
||||||
|
$escapedBase = preg_quote($baseUrl, '~');
|
||||||
|
|
||||||
|
// Public site-name link: /index/<locale>/index -> /
|
||||||
|
$rootUrl = htmlspecialchars($baseUrl . '/', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||||
|
$siteNamePattern =
|
||||||
|
'~(<div\s+class=["\']pkp_site_name["\'][^>]*>\s*<a\s+[^>]*href=["\'])' .
|
||||||
|
$escapedBase .
|
||||||
|
'/index/[^/"\']+/index/?(["\'])~is';
|
||||||
|
|
||||||
|
$output = preg_replace_callback(
|
||||||
|
$siteNamePattern,
|
||||||
|
static fn (array $matches): string => $matches[1] . $rootUrl . $matches[2],
|
||||||
|
$output,
|
||||||
|
1
|
||||||
|
) ?? $output;
|
||||||
|
|
||||||
|
// Clean ordinary site-level user links/actions in the
|
||||||
|
// Indonesian/default locale.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// /index/id/user/profile -> /user/profile
|
||||||
|
// /index/id/user/register -> /user/register
|
||||||
|
//
|
||||||
|
// Component/AJAX routes are not matched by this pattern.
|
||||||
|
$userPattern =
|
||||||
|
'~\b(href|action)=(["\'])' .
|
||||||
|
$escapedBase .
|
||||||
|
'/index/id/user(?:/([^"\'?#]*))?([^"\']*)\2~i';
|
||||||
|
|
||||||
|
$output = preg_replace_callback(
|
||||||
|
$userPattern,
|
||||||
|
static function (array $matches) use ($baseUrl): string {
|
||||||
|
$attribute = $matches[1];
|
||||||
|
$quote = $matches[2];
|
||||||
|
$tail = isset($matches[3]) ? trim($matches[3], '/') : '';
|
||||||
|
$suffix = $matches[4] ?? '';
|
||||||
|
|
||||||
|
$clean = $baseUrl . '/user';
|
||||||
|
if ($tail !== '' && $tail !== 'index') {
|
||||||
|
$clean .= '/' . $tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $attribute . '=' . $quote . $clean . $suffix . $quote;
|
||||||
|
},
|
||||||
|
$output
|
||||||
|
) ?? $output;
|
||||||
|
|
||||||
|
// Clean ordinary site-level Administration links/actions in the
|
||||||
|
// Indonesian/default locale. Query strings/fragments are preserved.
|
||||||
|
$adminPattern =
|
||||||
|
'~\b(href|action)=(["\'])' .
|
||||||
|
$escapedBase .
|
||||||
|
'/index/id/admin(?:/([^"\'?#]*))?([^"\']*)\2~i';
|
||||||
|
|
||||||
|
$output = preg_replace_callback(
|
||||||
|
$adminPattern,
|
||||||
|
static function (array $matches) use ($baseUrl): string {
|
||||||
|
$attribute = $matches[1];
|
||||||
|
$quote = $matches[2];
|
||||||
|
$tail = isset($matches[3]) ? trim($matches[3], '/') : '';
|
||||||
|
$suffix = $matches[4] ?? '';
|
||||||
|
|
||||||
|
// /admin and /admin/index both become the clean admin root.
|
||||||
|
if ($tail === '' || $tail === 'index') {
|
||||||
|
$clean = $baseUrl . '/admin';
|
||||||
|
} else {
|
||||||
|
$clean = $baseUrl . '/admin/' . $tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $attribute . '=' . $quote . $clean . $suffix . $quote;
|
||||||
|
},
|
||||||
|
$output
|
||||||
|
) ?? $output;
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace site-level account navigation URLs with clean aliases.
|
||||||
|
*
|
||||||
|
* @param string $hookName
|
||||||
|
* @param array $params [$navigationMenuItem, $navigationMenu]
|
||||||
|
*/
|
||||||
|
public function rewriteAccountMenuUrl($hookName, $params): bool
|
||||||
|
{
|
||||||
|
$navigationMenuItem = $params[0] ?? null;
|
||||||
|
if (!$navigationMenuItem instanceof NavigationMenuItem) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = Application::get()->getRequest();
|
||||||
|
|
||||||
|
// Do not alter account-menu routes while inside a journal context.
|
||||||
|
if ($request->getContext()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$baseUrl = rtrim($request->getBaseUrl(), '/');
|
||||||
|
|
||||||
|
switch ($navigationMenuItem->getType()) {
|
||||||
|
case NavigationMenuItem::NMI_TYPE_USER_DASHBOARD:
|
||||||
|
$navigationMenuItem->setUrl($baseUrl . '/dashboard');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NavigationMenuItem::NMI_TYPE_USER_PROFILE:
|
||||||
|
$navigationMenuItem->setUrl($baseUrl . '/profile');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NavigationMenuItem::NMI_TYPE_ADMINISTRATION:
|
||||||
|
$navigationMenuItem->setUrl($baseUrl . '/admin');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NavigationMenuItem::NMI_TYPE_USER_LOGOUT:
|
||||||
|
// When a site administrator is impersonating another user,
|
||||||
|
// preserve OJS' special signOutAsUser URL and behavior.
|
||||||
|
if (!Validation::loggedInAs()) {
|
||||||
|
$navigationMenuItem->setUrl($baseUrl . '/logout');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Clean User URLs for OJS 3.5
|
||||||
|
|
||||||
|
Targeted for the OJS 3.5.0-5 source tree supplied for testing.
|
||||||
|
|
||||||
|
The plugin changes the **site-level** account menu links and also rewrites the site-name/header link to `/`:
|
||||||
|
|
||||||
|
- Dashboard -> `/dashboard`
|
||||||
|
- Profile -> `/profile`
|
||||||
|
- Administration -> `/admin`
|
||||||
|
- Log Out -> `/logout`
|
||||||
|
- Site name/header -> `/`
|
||||||
|
|
||||||
|
Journal-context navigation is intentionally not changed.
|
||||||
|
|
||||||
|
## Required `.htaccess` aliases
|
||||||
|
|
||||||
|
Place these rules **before** the generic OJS catch-all rewrite rule:
|
||||||
|
|
||||||
|
```apache
|
||||||
|
RewriteRule ^dashboard/?$ index.php/index/id/index [L,QSA]
|
||||||
|
RewriteRule ^profile/?$ index.php/index/id/user/profile [L,QSA]
|
||||||
|
RewriteRule ^admin/?$ index.php/index/id/admin/index [L,QSA]
|
||||||
|
RewriteRule ^logout/?$ index.php/index/id/login/signOut [L,QSA]
|
||||||
|
```
|
||||||
|
|
||||||
|
Then keep the existing OJS rules, for example:
|
||||||
|
|
||||||
|
```apache
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^(.*)$ index.php/$1 [L,QSA]
|
||||||
|
```
|
||||||
|
|
||||||
|
If the configured site locale/path is changed from `id`, update the internal targets above accordingly.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Upload the ZIP from **Site Settings -> Plugins -> Upload A New Plugin**, then enable **Clean User URLs**.
|
||||||
|
|
||||||
|
If the plugin cannot be uploaded from the UI, extract `cleanUserUrls` into `plugins/generic/` and enable it from the plugin list.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.0.2
|
||||||
|
Fix output-filter callback compatibility with Smarty in OJS 3.5.0-5.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.0.4
|
||||||
|
Adds a conservative clean URL for the exact site-level **Administration** breadcrumb/home link:
|
||||||
|
|
||||||
|
- `/index/id/admin` -> `/admin`
|
||||||
|
- `/index/id/admin/index` -> `/admin`
|
||||||
|
|
||||||
|
It deliberately does **not** rewrite child admin URLs such as `/index/id/admin/settings`,
|
||||||
|
component (`$$$call$$$`) routes, API routes, or AJAX endpoints.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.0.5
|
||||||
|
Fixes the Administration-link matching bug in v1.0.0.4 and cleans ordinary
|
||||||
|
site-level Administration URLs while leaving component/API/AJAX routes alone.
|
||||||
|
|
||||||
|
Requires Apache aliases such as:
|
||||||
|
`/admin/contexts -> index.php/index/id/admin/contexts`.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.0.6
|
||||||
|
Adds clean ordinary site-level user URLs.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- `/index/id/user/profile` -> `/user/profile`
|
||||||
|
- `/index/id/user/register` -> `/user/register`
|
||||||
|
|
||||||
|
Requires:
|
||||||
|
`RewriteRule ^user/(.*)$ index.php/index/id/user/$1 [L,QSA]`
|
||||||
|
|
||||||
|
Component/AJAX (`$$$call$$$`) and API routes remain untouched.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.0.7
|
||||||
|
Fixes the backend **Edit Profile** link in OJS 3.5.
|
||||||
|
|
||||||
|
The backend top navigation is rendered by Vue after Smarty output filters run,
|
||||||
|
so the plugin now adds a small backend-only MutationObserver that changes only:
|
||||||
|
|
||||||
|
- `/index/id/user/profile` -> `/user/profile`
|
||||||
|
|
||||||
|
No API, component (`$$$call$$$`), journal-context, or profile-tab routes are modified.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0.0.8
|
||||||
|
Extends the backend Vue user-menu helper to clean both:
|
||||||
|
|
||||||
|
- `/index/id/user/profile` -> `/user/profile`
|
||||||
|
- `/index/id/login/signOut` -> `/logout`
|
||||||
|
|
||||||
|
No API, component (`$$$call$$$`), profile-tab, or journal-context routes are modified.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file plugins/generic/cleanUserUrls/index.php
|
||||||
|
* @brief Wrapper for the Clean User URLs plugin.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return new \APP\plugins\generic\cleanUserUrls\CleanUserUrlsPlugin();
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Clean User URLs 1.0.0.4\n"
|
||||||
|
"Language: en\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
msgid "plugins.generic.cleanUserUrls.displayName"
|
||||||
|
msgstr "Clean User URLs"
|
||||||
|
|
||||||
|
msgid "plugins.generic.cleanUserUrls.description"
|
||||||
|
msgstr "Replaces site-level account menu links with clean aliases for Dashboard, Profile, Administration, and Log Out. Web server rewrite rules are required."
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE version SYSTEM "../../../lib/pkp/dtd/pluginVersion.dtd">
|
||||||
|
<version>
|
||||||
|
<application>cleanUserUrls</application>
|
||||||
|
<type>plugins.generic</type>
|
||||||
|
<release>1.0.0.8</release>
|
||||||
|
<date>2026-08-16</date>
|
||||||
|
<lazy-load>1</lazy-load>
|
||||||
|
<class>CleanUserUrlsPlugin</class>
|
||||||
|
</version>
|
||||||
Reference in New Issue
Block a user