Skip to the content
Documentation
Help Start
Reading in:
  • German: 9,059 of 9,059 pages
About the help in other languages
All help pages

The content finder

Read this page if you need to find a content object when all you have is an id or a path (an object id from a log, a node id from a URL, the attribute id of an error message, the path of an image file on disk), to see…

Download as PDF

Address: https://doc.exponential.earth/exphelp/viewer/features/6.0/content-finder.md

On this page

The content finder

Read this page if you need to find a content object when all you have is an id or a path (an object id from a log, a node id from a URL, the attribute id of an error message, the path of an image file on disk), to see every version, location and trash entry of it, or to remove it from there; and if you decide who may do that.

In short

  • Setup > Content finder (/contentfinder/search), and a small search box below the content structure in the Content tab. One search field, and an optional selector for what the text is.
  • It finds the object behind an object, node, attribute or version id, a remote id, a URL alias, a page URL, or the path or URL of an image, binary or media file below var/.
  • Each result shows the object's class, name, owner, section, languages, states, remote id and dates; every version (id, number, status, language, creator, modified, with a link to the version view); every location (node id, path, URL alias, main, hidden); and, when it is in the trash, where it was, when and by whom it was trashed.
  • From the result: Move to trash, Delete permanently, Purge from the trash, Restore and Remove version. Each goes through a confirmation page that shows exactly what will happen; what cannot be undone needs an extra tick.
  • Two policies: contentfinder/search to use it at all, contentfinder/delete for its actions. Each result shows and offers only what the user's content policies allow; something the user may not see is answered exactly like a miss.
  • Administration siteaccesses only. Every search and every action is recorded in the audit.

Finding something

Type the id, path or URL and press Find. With Search for: Recognise automatically (the default) the text decides:

You typeIt is looked up as
4242an object id, a node id (in the tree and in the trash), an attribute id and a version id: every hit is shown
node:4242, object:4242, attribute:…, version:…, remote:…, alias:…, file:… (or n:, o:, a:, v:, r:, f:)that type only
https://www.example.com/admin/content/view/full/4242node 4242 (any view mode, with or without a siteaccess)
https://www.example.com/content/download/12/345/file/report.pdfattribute 345
var/site/storage/images/news/summer/1234-2-eng-GB/summer_large.jpg, or the full URL of itthe image file: the attribute, the object and its nodes
var/site/storage/original/application/4c1b…e9.pdfthe binary or media file of that name (and of that mime group)
var/site/storage/images/news/summer/ or …/original/application/*every file below that prefix (up to the row limit)
d41d8cd98f00b204e9800998ecf8427e (32 hexadecimal digits)a remote id
News/Summer-party, /News/Summer-party/, https://www.example.com/News/Summer-party?x=1a URL alias or a page address (exactly; see "URL aliases and siteaccesses" below)
any other worda remote id and a URL alias

Choose a type in the selector to look the text up as that only: Object id, Node id, Attribute id, Version id, Remote id, URL alias, File path.

Each result block starts with Found by: what matched. For an attribute or a file it names the attribute (id, identifier, datatype), the version and the language the match was in, and the file path.

An image is stored once per alias (…_large.jpg, …_small.jpg) and in every version that kept it; any of those paths finds it. A path to a file that is not referenced by an attribute (an orphan left in var/storage) finds nothing.

URL aliases and siteaccesses

A siteaccess with site.ini [SiteAccessSettings] PathPrefix shows its pages without the prefix: the alias stored as fit-healthy/showcase/test-product is the page /site/showcase/test-product of the siteaccess site with PathPrefix=fit-healthy. The finder takes all three: a URL alias or a page address is tried, exactly and in this order, until one resolves:

  1. the path as typed;

  2. the path as a page address: the siteaccess the kernel matches for it (eZSiteAccess::match(): by its first segment with URI matching, by the host of a full URL with host or host and URI matching), its segment taken off, and that siteaccess's own PathPrefix put in front, as eZURLAliasML does (not when the path already starts with it, nor when its first element is in PathPrefixExclude[]); then the same without the prefix;

  3. a first segment that names a siteaccess taken off, with that siteaccess's prefix in front, then without;

  4. the path with each distinct PathPrefix of the siteaccesses (AvailableSiteAccessList[] and RelatedSiteAccessList[]) in front.

  5. when none of these resolves: the last part of an alias. Every alias element whose own text is the last element of the path (compared without case, as eZURLAliasML compares: by the indexed text column, and only when that finds nothing by text_md5, the md5 of the lower-case text; never a LIKE), each checked upwards against the rest of the path (showcase/test-product must have showcase right above). Every node found is listed with its full path, up to ResultLimit; an old address kept as history is listed, and marked, only when nothing current matches. Found by reads "the last part of the URL alias <full path>". This is the one lookup that may read ezurlalias_ml without an index (the text_md5 step, when the text matches no stored text in its case).

  6. when that finds nothing either, and the text is one element (no slash): a part of an alias. product, test and test product find fit-healthy/showcase/test-product. The text is split into words at dashes, underscores and spaces; an alias element matches when it contains the words in that order with one dash, underscore or space between them, without case. The query is one LOWER( text ) LIKE '%…%' ESCAPE '!' on ezurlalias_ml (with %, _ and ! of the text escaped, so they are only themselves; on MongoDB a regular expression without case), and the finder then checks every row exactly. Current entries are taken first; old addresses (history) only when no current one matches, and marked. The closest matches come first: the element itself, then elements starting with the words, then the words as whole words, then at the start of a word, then inside a word; at most PartRowLimit elements (2000) are read, and at most ResultLimit objects shown (the page says when there were more). Found by reads "an alias containing “product” (full path)". A text shorter than MinimumPartLength (3 characters) is not searched as a part: the page says "Enter at least 3 characters to search by part of an alias".

    This is the one lookup that reads the whole alias table. Measured on a copy of a real installation's content (2833 alias rows; test matches 1768 of them): SQLite 10 ms, MariaDB 61 ms, PostgreSQL 55 ms, MongoDB 95 ms, Oracle 287 ms, reading every matching row. It runs only when nothing else matched; where the table is very large, switch it off with contentfinder.ini [ContentFinderSettings] PartialAliasSearch=disabled.

Each siteaccess's prefix is read from its own site.ini. Found by names the alias that resolved and, when a siteaccess or a prefix made the difference, which. Steps 1 to 5 are exact lookups, never a LIKE; for every step the rights decide per result as for every lookup.

What is accepted

  • Ids are whole numbers from 1 to 2147483647 (a leading # is allowed). Anything else is not an id: 1 OR 1=1 is looked up as a remote id and a URL alias, as text.
  • A URL is reduced to its path: scheme, host, query and fragment are dropped; the path is percent-decoded once and repeated slashes are made one. Only http, https and ftp URLs are taken.
  • A file path must be below var/. A path with a . or .. segment (also percent-encoded), a backslash, a NUL byte or another control character is refused. A prefix (a trailing / or *) must name at least var/<dir>/storage. A * anywhere else is refused.
  • Texts longer than 1024 bytes and texts that are not valid UTF-8 are refused.

How it looks things up

Every lookup is one query on an indexed column (or an exact match on a short one), with a hard row limit: ezcontentobject.id, ezcontentobject_tree.node_id, ezcontentobject_trash.node_id, ezcontentobject_attribute.id, ezcontentobject_version.id, the remote_id of objects, nodes and trashed nodes, eZURLAliasML::fetchNodeIDByPath(), ezimagefile.filepath, and filename and mime_type of ezbinaryfile and ezmedia. Integers are cast, texts are escaped by the database; a prefix is LIKE 'prefix%' ESCAPE '!' with %, _ and ! in the text escaped. There is no other LIKE '%text%' than the part of an alias (step 6 above). Remote ids and file paths match in their case on every engine: the database narrows (MySQL's collations and SQLite's LIKE ignore case), and the finder then compares the stored value exactly. URL aliases are case-insensitive, as eZURLAliasML resolves them.

At most ResultLimit objects (25) are described for one search, and at most FileRowLimit file rows (50) are read for one path or prefix; the page says when there were more.

A search is an address with view parameters, contentfinder/search/(type)/object/(text)/58: (text) is the text, written as every view parameter is (/ as ~2F, ( and ) as ~28 and ~29, the rest readable: (text)/example.com~2Fnews, see how a value travels; a link of before, ~ and a base64url, is still read) and (type) the type (none for auto). The form is sent with POST and answered with a 303 to that address; a query string such as ?SearchText= is not read.

Who may use it

PolicyWhat it allows
contentfinder/searchopen the finder (the Setup entry and the box show only with it) and search
contentfinder/deletethe actions of the finder; each needs its content policies as well (below)

No role but Administrator (which holds every policy) has them in a new installation. Give contentfinder/search to a support role; it shows that role nothing it could not see in the content structure, the trash or the version history. Give contentfinder/delete only to the roles that also remove content.

What each result shows

The finder never shows more than the user may see elsewhere. It asks the kernel's own checks:

PartShown when
the object (name, class, owner, section, languages, states, remote id, dates)content/read on the object (eZContentObject::canRead()), or a trash entry of it the user may see
a locationcontent/read on that node (eZContentObjectTreeNode::canRead())
a versioncontent/versionread on that version (eZContentObjectVersion::canVersionRead()); the published version also for who may read the object
a trash entrycontent/restore or content/cleantrash, and the trash list's own permission SQL (content/read where the node was)
a match by node, URL alias or node remote idthat node is one the user may read
a match by attribute, file path or version idthe version the match is in is one the user may see

A hit the user may not see is dropped. When nothing is left, the page says Nothing found, or you have no access to what was found., exactly as for an id that does not exist, so ids cannot be probed. (The audit record of the search counts the objects found but not shown; only audit/read sees it.)

The actions

ActionNeeds (besides contentfinder/search and contentfinder/delete)Done by
Move to trashcontent/read and content/remove on every location of the object and on everything below them; the object can go to the trash (no user below it)the content/delete operation with move_to_trash (as Remove in the content structure)
Delete permanentlyas Move to trashthe content/delete operation without the trash
Purge from the trashthe trash entry visible; content/cleantrash and content/removethe trash service's purge (as Empty trash for one object)
Restorecontent/restorethe kernel's own content/restore page, which the button opens
Remove versionwho may edit the object; content/versionremove on the version; a draft, archived, rejected or internal draft version (never the published one or one a workflow holds)eZContentObjectVersion::removeThis(), as the version history does
  • Every action is a POST from the result page and opens a confirmation page that lists exactly what will happen: each location with the number of nodes below it, the number of versions, other objects that relate to it. Nothing is done until Confirm is pressed there.
  • Delete permanently, Purge and Remove version cannot be undone: the confirmation needs the tick I understand that this cannot be undone. as well.
  • The confirmation is bound to the user's session, the action, the object and what the page showed: when anything changed in between (a node added below, the object moved to the trash by someone else), the action is refused with The object changed after the confirmation page was shown.
  • A removal with more nodes below it than content.ini [RemoveSettings] MaxNodesRemoveSubtree (100) is refused here: remove it from the content structure, where a large removal runs as a background job. A subtree a content job is working on, and one with something waiting for approval, is refused too.
  • After the action the finder shows the outcome and the object as it is now.

Requests that are refused

  • contentfinder/action answers 405 to anything but POST, and 403 to a POST without the form tokens: the ezformtoken token (checked for every POST, and again by the view) and the finder's own token, bound to the session and the user.
  • On a siteaccess that is not an administration one the finder answers 404 (a user without contentfinder/search gets the access denied page first).

Settings

settings/contentfinder.ini, overridden in settings/override/contentfinder.ini.append.php or a siteaccess:

[ContentFinderSettings]
# true | false: with false the finder answers 404 everywhere
Enabled=true
# The siteaccesses it answers on; empty: every siteaccess whose design list has an
# administration design (admin, admin2, admin3, admin4, admin4l, adminui, ...)
SiteAccessList[]
# At most this many objects per search (1 to 100)
ResultLimit=25
# At most this many file rows per path or prefix (1 to 500)
FileRowLimit=50
# The part of an alias: enabled | disabled, the shortest part, the most alias elements read
PartialAliasSearch=enabled
MinimumPartLength=3
PartRowLimit=2000

The Setup entry is menu.ini [Leftmenu_setup] Links[contentfinder] (with PolicyList_contentfinder[]=contentfinder/search); the box is design:contentfinder/box.tpl, included by parts/content/menu.tpl of the admin and admin4 designs (and so of admin2, admin3, admin4l and the editor siteaccess, which use them). A design with its own parts/content/menu.tpl includes the box with

{include uri='design:contentfinder/box.tpl'}

The audit

EventRecordedData
content.finder.searchevery search (channel content, info)the type chosen, the types tried, the text (up to 128 characters), the objects shown and the objects found but not shown; refused with the reason for a text that is not taken
content.finder.actionevery action done (channel content, notice), and every refusal of the action viewthe object, the target (the nodes, the trash entry or the version), the action, the locations and the nodes below; the kernel's own removal events (content.node.remove.trash, content.object.remove, content.object.purge, content.version.remove) are recorded as its children

The actor is the signed-in user, as for every audit event. See the audit trail.

For developers

  • expContentFinderInput::parse( $text, $type ): the recognition, without a database.
  • ( new expContentFinder() )->search( $text, $type ): the results for the current user; expContentFinder::isAvailableHere(), expContentFinder::hasPolicy( $module, $function ).
  • expContentFinderAction::plan( $action, $objectID, $versionID ) and ::execute( ..., $confirmToken ).
  • The views are Exponential\View\Kernel\Contentfinder\Search and \Action (Action::decide() is the request check, without side effects).
  • Tests: tests/tests/kernel/contentfinder/ (--testsuite kernel-contentfinder). expContentFinderInputTest needs nothing; expContentFinderSandboxTest writes content, users and roles (fixtures/expcontentfindertestfixtures.php), so it runs only in a throwaway copy of an installation named by EXP_CONTENTFINDER_SANDBOX and is skipped everywhere else. On another engine than SQLite it also needs EXP_CONTENTFINDER_SANDBOX_DATABASE, naming the throwaway database (the Database of the settings, or for Oracle the User). expContentFinderURLAliasTest needs nothing.

Databases

Every lookup, the rights per result, the four actions and the audit records are tested on each engine below, with --testsuite kernel-contentfinder and an end-to-end browser check, in a throwaway database holding a copy of a real installation's content:

EngineVersion testedNotes
SQLite3.34.1
MySQL / MariaDBMariaDB 10.5.29its collations compare without case: remote ids and file paths are compared exactly by the finder
PostgreSQL13.23LIKE compares in case, as the finder does; URL aliases need digest() (pgcrypto), which the installer sets up
OracleOracle AI Database 26ai Free (23.26.3), with extension/ezoracle 2.3.5LIKE … ESCAPE and LOWER() as everywhere; no difference found
MongoDB8.3.11through the driver: LIKE … ESCAPE, LOWER( column ) (the user fetch) and URL alias paths (eZURLAliasML::fetchByPath(), one element at a time instead of the join) are translated; every finder query is a single-table one

Become an Exponential Partner

Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.

Something wrong on this page? Report an error

Documentation license

This documentation is free documentation, licensed under the GNU Free Documentation License 1.3 or later.

Exponential Copyright © 1998-2026 Exponential Foundation and 7x