⬇️ Install package: OJS 3.5 · OJS 3.3 — or browse all Releases.
The "Most read articles by the same author(s)" section on the article page — the same feature journals already know, rebuilt so that it is read from a cache instead of computed while a reader waits.
Originally written by the Public Knowledge Project — Simon Fraser University and John Willinsky — and distributed with OJS. This is a rewrite of their plugin for OJS 3.5, keeping the feature and the markup and changing where the work happens; maintained by OJSBR. Details in Credits & authorship.
| OJS version | Branch | Plugin release |
|---|---|---|
| OJS 3.5.x | stable-3_5_0 (default) |
2.0.0.1 |
| OJS 3.3.x | stable-3_3_0 |
2.0.0.1-ojs3.3 |
The original plugin asks, on every single view of an article: "which authors are named
like this one?". Author names live in author_settings, whose only key is author_id, so
that question has no index to stand on — MySQL scans the whole settings table, twice per
author of the article being read.
Measured on a production journal with 4,823 published articles and 25,877 authors
(EXPLAIN reports type=ALL over 401,192 rows):
| Article | Authors | Time | Queries |
|---|---|---|---|
| worst case found | 32 | 11.9 s | 1,441 |
| second worst | 21 | 6.4 s | 410 |
| average of 25 | 4 | 0.8 s | 183 |
Every one of those seconds is spent on the most visited page of the site, and it gets worse as the journal grows. That is why so many hosts simply delete this plugin.
The same section, from a list that is already written down:
| original | this plugin (cache miss) | this plugin (cache hit) | |
|---|---|---|---|
| worst case above | 11.9 s / 1,441 queries | 16.6 ms / 49 queries | 0.14 ms / 1 query |
| whole journal rebuilt | — | 12.2 s for 4,823 articles | — |
It also finds more than the original, and the difference is not noise. Compared over 60 submissions of that journal, the new results are a superset: nothing the original found was lost, and these were added —
- names with stray or double spaces. The original compares strings exactly, so
"Ana Carolina Menezes"and"Ana Carolina Menezes"are two different people. Real, common data. - ORCID. An author who changed surname keeps their identifier. The original plugin's own source comments asked for exactly this ("until OJS allows users to consistently normalize authors … via ORCID"); OJS 3.5 has it, so it is used — as an additional key, never a replacement, so switching it on can only add articles.
- the same name recorded in different locales.
And it stops recommending nonsense: the original calls filterByName('', '') for authors
whose name is blank, which matches every other nameless author in the database. In that same
journal there are 222 nameless authors across 97 published articles, and each of those
articles was being given about 3,285 irrelevant recommendations at a cost of roughly
100 seconds per view.
- Download the package from Releases and install it through
Settings → Website → Plugins → Upload A New Plugin, or drop the folder into
plugins/generic/recommendByAuthorand register it. Do not rename the folder — OJS derives the plugin's namespace from the directory name. - Enable it in Settings → Website → Plugins. Enabling creates its three tables.
- The section will not appear straight away — see After enabling below for how long the filling takes and how to do it in one pass instead of waiting.
php plugins/generic/recommendByAuthor/tools/buildRecommendations.php --pause=100Run it as the account that owns the files, never as root. --help lists the options
(--context, --limit, --batch, --pause, --stale-only, --status).
This is expected, and it is the plugin working as designed. Enabling creates the tables but leaves them empty; nothing is computed while a reader waits. Until an article has been processed, it simply shows no section.
How long the filling takes depends on how the site runs its scheduled tasks:
| pace | a journal of 1,000 articles | of 5,000 | |
|---|---|---|---|
| With cron (recommended) | 250 articles every 15 min | about 1 hour | about 5 hours |
Without cron ([schedule] task_runner, the default) |
up to 250 articles a minute, but only while people are visiting the site | minutes to hours, depending on traffic | idem |
| By hand, once | the whole journal in one pass | seconds | 12 s on the journal this was measured on |
If you would rather not wait, build it once and let the task only keep it current:
php plugins/generic/recommendByAuthor/tools/buildRecommendations.php --pause=100You can watch it fill up in Settings → Website → Plugins → Recommend Articles by Author →
Settings, which reports how many submissions already have recommendations, or from the command
line with tools/buildRecommendations.php --status.
Three, all of them new — no OJS table is modified:
| table | what it holds |
|---|---|
recommend_author_index |
one row per (author identity, published submission). The identity is a normalised name or an ORCID; this is what turns "which articles does this author have?" into an index lookup |
recommend_author_cache |
the ordered list of recommended submissions for each article |
recommend_author_state |
what has been indexed and computed, and when |
On a journal with 4,823 published articles and 25,877 authors they take about 17 MB together.
Every row is tied to its submission with ON DELETE CASCADE, so deleting a submission cleans up
after itself, and uninstalling is a matter of dropping the three tables.
Settings → Website → Plugins → Recommend Articles by Author → Settings. The panel shows how much of the journal is covered, and lets you set:
| Setting | Default | What it is for |
|---|---|---|
| Recommendations per page | 10 | What the reader sees at a time |
| Maximum stored per article | 50 | How deep paging can go |
| Order by | Most read | Views and downloads, or most recently published |
| Also match by ORCID | on | Adds articles the name alone would miss |
| Submissions refreshed per run | 250 | The size of each slice |
| Refresh older than | 7 days | How stale a list may get |
| Enrol at most | 0 (no limit) | Cap for trying the plugin out on part of a journal |
| Reuse reading figures for | 12 h | How often the ranking aggregate is recomputed |
| Keep the rendered section for | 168 h | Lifetime of the rendered HTML |
| Compute while the reader waits | off | See below |
Leave "compute while the reader waits" off on any journal of size. With it off, an article that has not been computed yet shows no section at all and costs one indexed query — there is no path from a page view to expensive work.
Three layers, none of which lets the article page do real work:
- Rendered HTML in the Laravel cache, keyed by submission, state version, settings stamp, locale and page. A hit is one primary-key read.
recommend_author_cache— the ordered list of recommended submission ids.recommend_author_index— one row per (author identity, published submission), where the identity is a normalised name or an ORCID. This is what turns "which articles does this author have?" from a table scan into an index lookup.
recommend_author_state records what has been indexed and computed. Indexing and computing
are deliberately separate: an article can only be recommended once it is in the index, so
computing while the index is still being filled would store lists that are quietly incomplete
— and store them as finished. Indexing runs to completion first (1.3 s for the whole journal
above), and only then is anything computed.
The scheduled task (RefreshRecommendations, every 15 minutes) refreshes a slice: the
never-computed first, then the least recently computed, up to the batch size. Nothing ever
expires at the same moment, so the refresh is never a stampede — the window rolls forward.
Publishing, unpublishing or deleting an article queues it and everything that shares an author
with it, so new work appears without waiting for the cycle.
No OJS table is modified. Uninstalling is DROP.
Three suites, all of them run against OJS 3.5.0.3 before this release (details and the list of cases in tests/CASES.md):
| Suite | What it covers | Result |
|---|---|---|
tests/regression.php |
author keys, the index, the store, and publishing through to the reader — against a real database | 62 cases, 62 passed |
tests/AuthorKeyTest.php |
the identity rules on their own (PHPUnit, no database) | passed |
cypress/tests/functional/ |
enabling, the settings screen and the article page, in a browser | 7 tests, 7 passed |
php plugins/generic/recommendByAuthor/tests/regression.php
cd lib/pkp/tests && ../lib/vendor/bin/phpunit -c phpunit.xml --testsuite ApplicationPlugins
npx cypress run --spec 'cypress/tests/**/RecommendByAuthor.cy.js'The regression suite creates its own submissions and deletes them again; run it on a test
installation. The Cypress spec defaults to the PKP test data but runs against any journal
through cypress.env.json — it solves the Altcha proof of work where captcha_on_login is on,
and works whatever language the interface is in.
Beyond the suites, the correctness of the results was checked against the original plugin on a production journal: 60 submissions compared item by item, where the new results are a superset with no legitimate result lost, and every extra match audited by hand.
- Public Knowledge Project, Simon Fraser University and John
Willinsky — authors of the original
recommendByAuthorplugin, of the feature itself and of the template markup this plugin keeps. Copyright (c) 2014–2025 Simon Fraser University, (c) 2003–2025 John Willinsky. - OJSBR — the 3.5 rewrite: author index, materialised cache, the scheduled slice-refresh, ORCID matching and the settings panel.
Issues and pull requests are welcome — see CONTRIBUTING.md.
GNU GPL v3 — see LICENSE, the same licence as OJS and as the original plugin.
A seção "Artigos mais lidos pelo mesmo(s) autor(es)" na página do artigo — a mesma funcionalidade que as revistas já conhecem, reconstruída para ser lida de um cache em vez de calculada enquanto o leitor espera.
Escrito originalmente pelo Public Knowledge Project — Simon Fraser University e John Willinsky — e distribuído com o OJS. Esta é uma reescrita do plugin deles para o OJS 3.5, que mantém a funcionalidade e a marcação e muda onde o trabalho acontece; mantida pela OJSBR. Detalhes em Créditos e autoria.
| Versão do OJS | Branch | Versão do plugin |
|---|---|---|
| OJS 3.5.x | stable-3_5_0 (padrão) |
2.0.0.1 |
| OJS 3.3.x | stable-3_3_0 |
2.0.0.1-ojs3.3 |
O plugin original pergunta, a cada visualização de artigo: "quais autores se chamam
assim?". Os nomes ficam em author_settings, cuja única chave é author_id — então a
pergunta não tem índice em que se apoiar e o MySQL varre a tabela inteira, duas vezes por
autor do artigo.
Medido numa revista de produção com 4.823 artigos publicados e 25.877 autores:
| original | este plugin (sem cache de HTML) | este plugin (com cache) | |
|---|---|---|---|
| pior caso (artigo com 32 autores) | 11,9 s / 1.441 consultas | 16,6 ms / 49 consultas | 0,14 ms / 1 consulta |
| revista inteira reconstruída | — | 12,2 s para 4.823 artigos | — |
E encontra mais que o original, sem perder nada: comparados 60 artigos item a item, o
resultado novo é um superconjunto. Ele acrescenta nomes com espaço sobrando ou duplo (o
original compara string exata), casamento por ORCID (o autor que mudou de sobrenome) e
nomes gravados em locales diferentes. E deixa de recomendar lixo: o original chama
filterByName('', '') para autores sem nome, casando com todos os outros autores sem nome do
banco — naquela revista são 222 autores sem nome em 97 artigos, cada um recebendo cerca de
3.285 recomendações irrelevantes a um custo de uns 100 segundos por visualização.
- Baixe o pacote em Releases e instale por Configurações → Website →
Plugins → Enviar novo plugin, ou copie a pasta para
plugins/generic/recommendByAuthor. Não renomeie a pasta — o OJS deriva o namespace do nome do diretório. - Habilite em Configurações → Website → Plugins. Ao habilitar, as três tabelas são criadas.
- A seção não aparece de imediato — veja Depois de habilitar, abaixo. Para preencher de uma vez, em vez de esperar:
php plugins/generic/recommendByAuthor/tools/buildRecommendations.php --pause=100Rode como o dono dos arquivos, nunca como root. --help lista as opções.
Isso é esperado, e é o plugin funcionando como projetado. Habilitar cria as tabelas, mas elas nascem vazias; nada é calculado com o leitor esperando. Enquanto um artigo não tiver sido processado, ele simplesmente não exibe a seção.
Quanto tempo leva para preencher depende de como o site executa as tarefas agendadas:
| ritmo | revista de 1.000 artigos | de 5.000 | |
|---|---|---|---|
| Com cron (recomendado) | 250 artigos a cada 15 min | cerca de 1 hora | cerca de 5 horas |
Sem cron ([schedule] task_runner, o padrão) |
até 250 artigos por minuto, mas só enquanto houver visitas ao site | de minutos a horas, conforme o tráfego | idem |
| Na mão, uma vez | a revista inteira de uma vez | segundos | 12 s na revista onde isto foi medido |
Se preferir não esperar, monte de uma vez e deixe a tarefa só mantendo em dia:
php plugins/generic/recommendByAuthor/tools/buildRecommendations.php --pause=100Dá para acompanhar o preenchimento em Configurações → Website → Plugins → Artigos recomendados
por Autor → Configurações, que mostra quantas submissões já têm recomendações, ou pela linha de
comando com tools/buildRecommendations.php --status.
Três, todas novas — nenhuma tabela do OJS é alterada:
| tabela | o que guarda |
|---|---|
recommend_author_index |
uma linha por (identidade de autor, artigo publicado). A identidade é um nome normalizado ou um ORCID; é o que transforma "quais artigos este autor tem?" em consulta por índice |
recommend_author_cache |
a lista ordenada de artigos recomendados para cada artigo |
recommend_author_state |
o que já foi indexado e calculado, e quando |
Numa revista com 4.823 artigos publicados e 25.877 autores, as três somam cerca de 17 MB.
Cada linha está presa à sua submissão com ON DELETE CASCADE, então excluir uma submissão limpa
o que era dela, e desinstalar é apagar as três tabelas.
Em Configurações → Website → Plugins → Artigos recomendados por Autor → Configurações. O painel mostra a cobertura atual e permite ajustar recomendações por página, máximo armazenado por artigo, ordenação (mais lidos ou mais recentes), casamento por ORCID, tamanho do lote por execução, idade máxima antes de recalcular, teto de submissões inscritas e a duração dos caches.
Deixe "calcular enquanto o leitor espera" desligado em qualquer revista de porte: assim um artigo ainda não calculado simplesmente não exibe a seção e custa uma consulta indexada — não existe caminho entre uma visita e trabalho caro.
Três camadas: o HTML renderizado no cache do Laravel; a tabela recommend_author_cache com a
lista pronta de ids; e o índice recommend_author_index, uma linha por (identidade de autor,
artigo publicado), sendo a identidade um nome normalizado ou um ORCID — é ele que transforma a
varredura de tabela em consulta por chave.
Indexar e calcular são fases separadas de propósito: um artigo só pode ser recomendado depois de estar no índice, então calcular com o índice pela metade gravaria listas incompletas como se fossem finais. A indexação termina primeiro (1,3 s na revista inteira) e só então se calcula.
A tarefa agendada roda a cada 15 minutos e atualiza uma fatia: primeiro os nunca calculados, depois os mais antigos, até o tamanho do lote. Nada expira ao mesmo tempo, então a renovação nunca vira uma avalanche. Publicar, despublicar ou excluir um artigo coloca na fila ele e todos que compartilham autor com ele.
Nenhuma tabela do OJS é alterada. Desinstalar é DROP.
Três suítes, todas executadas contra o OJS 3.5.0.3 antes desta versão (a lista de casos está em
tests/CASES.md): a de regressão (tests/regression.php), que cobre as chaves
de autor, o índice, o armazenamento e o caminho da publicação até o leitor contra um banco real
— 62 casos, 62 passaram; a unitária em PHPUnit (tests/AuthorKeyTest.php), que cobre as
regras de identidade sozinhas — passou; e a funcional em Cypress, que cobre habilitar o
plugin, a tela de configurações e a página do artigo no navegador — 7 testes, 7 passaram.
A suíte de regressão cria e apaga as próprias submissões: rode em instalação de teste. O Cypress
usa por padrão a base de testes da PKP, mas roda contra qualquer revista via cypress.env.json
— ele resolve o desafio do Altcha onde captcha_on_login está ligado e funciona em qualquer
idioma de interface.
Além das suítes, a correção dos resultados foi conferida contra o plugin original numa revista de produção: 60 artigos comparados item a item, com o resultado novo sendo superconjunto, sem perder nada legítimo, e cada casamento extra auditado à mão.
- Public Knowledge Project, Simon Fraser University e John Willinsky
— autores do plugin
recommendByAuthororiginal, da funcionalidade e da marcação do template que este plugin preserva. Copyright (c) 2014–2025 Simon Fraser University, (c) 2003–2025 John Willinsky. - OJSBR — a reescrita para o 3.5: índice de autores, cache materializado, atualização em fatias pela tarefa agendada, casamento por ORCID e o painel de configurações.
GNU GPL v3 — veja LICENSE, a mesma licença do OJS e do plugin original.