@prefix sioc: <http://rdfs.org/sioc/ns#> .
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix content: <http://purl.org/rss/1.0/modules/content/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<https://lhero.org/?post_type=lh-portfolio-doc&#038;p=152642>
  a sioc:Post ;
  dc:title "Architecture" ;
  dcterms:identifier 152642 ;
  dc:modified "2026-10-27T12:48:14Z"^^xsd:dateTime ;
  dc:created "2026-10-27T12:43:04Z"^^xsd:dateTime ;
  sioc:link <https://lhero.org/portfolio/lh-private-uploads/architecture/> ;
  sioc:has_creator <https://lhero.org/author/1/#account> ;
  sioc:has_container <https://lhero.org/#posts> ;
  content:encoded "<ul class=\"lh_portfolio-meta\"><li><strong>Part of:</strong> <a href=\"https://lhero.org/portfolio/lh-private-uploads/\">LH Private Uploads</a></li></ul><h2 class=\"wp-block-heading\">Directory layout</h2><p class=\"wp-block-paragraph\">The private tree mirrors the public uploads layout. The main site&#8217;s private files sit at the root of the private directory (<code>private-uploads/2026/10/file.pdf</code>); every other site&#8217;s sit under <code>sites/ID/</code> (<code>private-uploads/sites/60/2026/10/file.pdf</code>), exactly as its public uploads sit under <code>uploads/sites/ID/</code>. Proxy URLs carry the same path below <code>/protected-files/</code>.</p><p class=\"wp-block-paragraph\">Releases before 1.5.0 put every site&#8217;s private files at the root, alongside the main site&#8217;s. On a subsite, a file still there is a legacy flat file (<code>LH_Private_Uploads_Paths::is_legacy_flat_path()</code>). It stays readable through the proxy only while that site&#8217;s own postmeta holds its exact path, can still be moved back to public, and once moved into its site&#8217;s directory, a request for its old flat URL is redirected (301) to the new one.</p><h2 class=\"wp-block-heading\">Classes</h2><ul class=\"wp-block-list\"><li><strong>Paths</strong>: all path and URL mapping. <code>private_site_dir()</code> is the current site&#8217;s own directory; <code>public_to_private()</code> and <code>private_to_public()</code> swap only a leading directory; <code>path_belongs_to_current_site()</code> checks a path is in the site&#8217;s own directory (and, on the main site, not in <code>sites/</code>). Also the <code>/protected-files/</code> rewrite rule and the <code>wp_get_attachment_url</code> filter that returns proxy URLs.</li><li><strong>Mover</strong>: moves a file and its image sizes between public and private on status changes (the parent post&#8217;s transition, a new attachment, an attachment edit or re-parenting), then updates <code>_wp_attached_file</code> to the absolute path and the guid. A move never overwrites an existing file: it fails with <code>target_exists</code>.</li><li><strong>Proxy</strong>: resolves a <code>/protected-files/</code> request, refuses anything outside the private tree (404) or another site&#8217;s directory (403), looks the attachment up by exact absolute path in this site&#8217;s postmeta, runs the password or capability check and streams the file.</li><li><strong>Access</strong>: the default access rules for the built-in private status, behind the access filters.</li><li><strong>Metabox</strong> and <strong>Admin</strong>: the File Access control on attachments, and a network admin migration scan that moves files to match their status.</li></ul><h2 class=\"wp-block-heading\">Filters</h2><ul class=\"wp-block-list\"><li><code>lh_private_uploads_protected_statuses</code>: post statuses whose files are kept private. Default <code>private</code>.</li><li><code>lh_private_uploads_user_can_access</code>: whether the current user may open a protected file. Starts false.</li><li><code>lh_private_uploads_attachment_is_protected</code>: per-attachment override of that decision.</li><li><code>lh_private_uploads_password_form</code>: the password form HTML.</li><li><code>lh_private_uploads_metabox_statuses</code> and <code>lh_private_uploads_unregister_attachment_statuses</code>: the statuses offered in the File Access control.</li></ul><h2 class=\"wp-block-heading\">Constants</h2><ul class=\"wp-block-list\"><li><code>LH_PRIVATE_UPLOADS_DIR</code>: the private directory. Default <code>private-uploads</code> one level above the WordPress root.</li><li><code>LH_PRIVATE_UPLOADS_XSENDFILE</code>: true to stream files with X-Sendfile (mod_xsendfile) instead of PHP.</li></ul>"^^rdf:XMLLiteral ;
  sioc:content "Part of: LH Private UploadsDirectory layoutThe private tree mirrors the public uploads layout. The main site&#8217;s private files sit at the root of the private directory (private-uploads/2026/10/file.pdf); every other site&#8217;s sit under sites/ID/ (private-uploads/sites/60/2026/10/file.pdf), exactly as its public uploads sit under uploads/sites/ID/. Proxy URLs carry the same path below /protected-files/.Releases before 1.5.0 put every site&#8217;s private files at the root, alongside the main site&#8217;s. On a subsite, a file still there is a legacy flat file (LH_Private_Uploads_Paths::is_legacy_flat_path()). It stays readable through the proxy only while that site&#8217;s own postmeta holds its exact path, can still be moved back to public, and once moved into its site&#8217;s directory, a request for its old flat URL is redirected (301) to the new one.ClassesPaths: all path and URL mapping. private_site_dir() is the current site&#8217;s own directory; public_to_private() and private_to_public() swap only a leading directory; path_belongs_to_current_site() checks a path is in the site&#8217;s own directory (and, on the main site, not in sites/). Also the /protected-files/ rewrite rule and the wp_get_attachment_url filter that returns proxy URLs.Mover: moves a file and its image sizes between public and private on status changes (the parent post&#8217;s transition, a new attachment, an attachment edit or re-parenting), then updates _wp_attached_file to the absolute path and the guid. A move never overwrites an existing file: it fails with target_exists.Proxy: resolves a /protected-files/ request, refuses anything outside the private tree (404) or another site&#8217;s directory (403), looks the attachment up by exact absolute path in this site&#8217;s postmeta, runs the password or capability check and streams the file.Access: the default access rules for the built-in private status, behind the access filters.Metabox and Admin: the File Access control on attachments, and a network admin migration scan that moves files to match their status.Filterslh_private_uploads_protected_statuses: post statuses whose files are kept private. Default private.lh_private_uploads_user_can_access: whether the current user may open a protected file. Starts false.lh_private_uploads_attachment_is_protected: per-attachment override of that decision.lh_private_uploads_password_form: the password form HTML.lh_private_uploads_metabox_statuses and lh_private_uploads_unregister_attachment_statuses: the statuses offered in the File Access control.ConstantsLH_PRIVATE_UPLOADS_DIR: the private directory. Default private-uploads one level above the WordPress root.LH_PRIVATE_UPLOADS_XSENDFILE: true to stream files with X-Sendfile (mod_xsendfile) instead of PHP." ;
  sioc:topic <https://lhero.org/?taxonomy=author&term=cap-1> .

<https://lhero.org/author/1/#account> rdfs:seeAlso <https://lhero.org/author/1/?feed=lhrdf&format=turtle> .
<https://lhero.org/?taxonomy=author&term=cap-1> rdfs:seeAlso <https://lhero.org/?taxonomy=author&term=cap-1&feed=lhrdf&format=turtle> .
