├── .editorconfig ├── .gitignore ├── .prettierrc ├── LICENSE.md ├── README.md ├── composer.json ├── composer.lock └── src ├── Console └── MediaGenerateSlugsCommand.php ├── DisableMediaPages.php └── DisableMediaPagesServiceProvider.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.php] 15 | indent_size = 4 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Brandon Nifong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Acorn Disable Media Pages 2 | 3 | ![Latest Stable Version](https://img.shields.io/packagist/v/log1x/acorn-disable-media-pages.svg?style=flat-square) 4 | ![Total Downloads](https://img.shields.io/packagist/dt/log1x/acorn-disable-media-pages.svg?style=flat-square) 5 | ![Build Status](https://img.shields.io/github/actions/workflow/status/log1x/acorn-disable-media-pages/main.yml?branch=main&style=flat-square) 6 | 7 | Disable Media Pages is an [Acorn](https://github.com/roots/acorn) package for WordPress that disables media attachment pages and generates unique UUID's for each media attachment's `post_name` to prevent clashing. 8 | 9 | WordPress 6.4 will bring the ability to ["disable" attachment pages](https://make.wordpress.org/core/2023/10/16/changes-to-attachment-pages/) but attachments will still generate unnecessary slugs based on the uploaded media's filename. 10 | 11 | ## Features 12 | 13 | - Replaces media attachment `post_name` slugs with randomly generated UUID's with [`Str::uuid()`](https://laravel.com/docs/10.x/strings#method-str-uuid). 14 | - Replaces the "View" media URL with a direct link to the media. 15 | - 404's any requests made to media attachment page slugs. 16 | - Easily convert/revert existing media page slugs using Acorn's CLI. 17 | 18 | ## Requirements 19 | 20 | - [PHP](https://secure.php.net/manual/en/install.php) >= 8.1 21 | - [Acorn](https://github.com/roots/acorn) >= 3.0 22 | 23 | ## Installation 24 | 25 | Install via Composer: 26 | 27 | ```bash 28 | $ composer require log1x/acorn-disable-media-pages 29 | ``` 30 | 31 | ## Usage 32 | 33 | ### Getting Started 34 | 35 | This package has no configuration and will start working once installed. 36 | 37 | ### Existing Media 38 | 39 | To convert existing media, simply use the following Acorn command: 40 | 41 | ```bash 42 | $ wp acorn media:generate-slugs 43 | ``` 44 | 45 | ### Reverting Slugs 46 | 47 | > [!WARNING] 48 | > Reverting your attachment `post_name` slugs may not restore them 1:1 to what they were prior to conversion. 49 | 50 | This command simply re-generates the slugs for each media attachment based on the existing `post_title` falling back to the filename if the existing `post_title` is empty. 51 | 52 | ```bash 53 | $ wp acorn media:generate-slugs --revert 54 | ``` 55 | 56 | ## Bug Reports 57 | 58 | If you discover a bug in Acorn Disable Media Pages, please [open an issue](https://github.com/log1x/acorn-disable-media-pages/issues). 59 | 60 | ## Contributing 61 | 62 | Contributing whether it be through PRs, reporting an issue, or suggesting an idea is encouraged and appreciated. 63 | 64 | ## License 65 | 66 | Acorn Disable Media Pages is provided under the [MIT License](LICENSE.md). 67 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "log1x/acorn-disable-media-pages", 3 | "type": "package", 4 | "license": "MIT", 5 | "description": "Disable media attachment pages in WordPress.", 6 | "authors": [ 7 | { 8 | "name": "Brandon Nifong", 9 | "email": "brandon@tendency.me" 10 | } 11 | ], 12 | "keywords": [ 13 | "wordpress", 14 | "acorn" 15 | ], 16 | "support": { 17 | "issues": "https://github.com/log1x/acorn-disable-media-pages/issues" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "Log1x\\DisableMediaPages\\": "src/" 22 | } 23 | }, 24 | "require": { 25 | "php": ">=8.1" 26 | }, 27 | "require-dev": { 28 | "laravel/pint": "^1.13" 29 | }, 30 | "extra": { 31 | "acorn": { 32 | "providers": [ 33 | "Log1x\\DisableMediaPages\\DisableMediaPagesServiceProvider" 34 | ] 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "314b70d9170f6826df893c515175dd7a", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "laravel/pint", 12 | "version": "v1.13.5", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/laravel/pint.git", 16 | "reference": "df105cf8ce7a8f0b8a9425ff45cd281a5448e423" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/laravel/pint/zipball/df105cf8ce7a8f0b8a9425ff45cd281a5448e423", 21 | "reference": "df105cf8ce7a8f0b8a9425ff45cd281a5448e423", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "ext-json": "*", 26 | "ext-mbstring": "*", 27 | "ext-tokenizer": "*", 28 | "ext-xml": "*", 29 | "php": "^8.1.0" 30 | }, 31 | "require-dev": { 32 | "friendsofphp/php-cs-fixer": "^3.34.1", 33 | "illuminate/view": "^10.26.2", 34 | "laravel-zero/framework": "^10.1.2", 35 | "mockery/mockery": "^1.6.6", 36 | "nunomaduro/larastan": "^2.6.4", 37 | "nunomaduro/termwind": "^1.15.1", 38 | "pestphp/pest": "^2.20.0" 39 | }, 40 | "bin": [ 41 | "builds/pint" 42 | ], 43 | "type": "project", 44 | "autoload": { 45 | "psr-4": { 46 | "App\\": "app/", 47 | "Database\\Seeders\\": "database/seeders/", 48 | "Database\\Factories\\": "database/factories/" 49 | } 50 | }, 51 | "notification-url": "https://packagist.org/downloads/", 52 | "license": [ 53 | "MIT" 54 | ], 55 | "authors": [ 56 | { 57 | "name": "Nuno Maduro", 58 | "email": "enunomaduro@gmail.com" 59 | } 60 | ], 61 | "description": "An opinionated code formatter for PHP.", 62 | "homepage": "https://laravel.com", 63 | "keywords": [ 64 | "format", 65 | "formatter", 66 | "lint", 67 | "linter", 68 | "php" 69 | ], 70 | "support": { 71 | "issues": "https://github.com/laravel/pint/issues", 72 | "source": "https://github.com/laravel/pint" 73 | }, 74 | "time": "2023-10-26T09:26:10+00:00" 75 | } 76 | ], 77 | "aliases": [], 78 | "minimum-stability": "stable", 79 | "stability-flags": [], 80 | "prefer-stable": false, 81 | "prefer-lowest": false, 82 | "platform": { 83 | "php": "^8.0" 84 | }, 85 | "platform-dev": [], 86 | "plugin-api-version": "2.3.0" 87 | } 88 | -------------------------------------------------------------------------------- /src/Console/MediaGenerateSlugsCommand.php: -------------------------------------------------------------------------------- 1 | package = app('Log1x/DisableMediaPages'); 54 | 55 | if ($this->option('revert')) { 56 | $this->handleRevert(); 57 | 58 | return; 59 | } 60 | 61 | $this->handleUpdate(); 62 | } 63 | 64 | /** 65 | * Handle the UUID slug update. 66 | * 67 | * @return void 68 | */ 69 | protected function handleUpdate() 70 | { 71 | $attachments = $this->getAttachments(); 72 | 73 | if ($attachments->isEmpty()) { 74 | $this->components->info('All media attachments already have UUID slugs.'); 75 | 76 | return; 77 | } 78 | 79 | if (! $this->components->confirm("Are you sure you want to update {$attachments->count()} media attachments?")) { 80 | return; 81 | } 82 | 83 | $timer = microtime(true); 84 | 85 | $attachments->each(function ($attachment) { 86 | $filename = basename($attachment['url']); 87 | 88 | if (Str::isUuid($attachment['name'])) { 89 | $this->components->warn("{$filename} ({$attachment['name']}) is already a UUID."); 90 | 91 | return; 92 | } 93 | 94 | $uuid = (string) Str::uuid(); 95 | 96 | wp_update_post([ 97 | 'ID' => $attachment['id'], 98 | 'post_name' => $uuid, 99 | ]); 100 | 101 | $this->components->info("{$filename} ({$attachment['name']}) has been updated to {$uuid}."); 102 | 103 | $this->count++; 104 | }); 105 | 106 | $timer = round(microtime(true) - $timer, 2); 107 | 108 | $this->newLine(); 109 | $this->components->info("Successfully updated {$this->count} media attachments in {$timer} second(s)."); 110 | } 111 | 112 | /** 113 | * Handle the UUID slug revert. 114 | * 115 | * @return void 116 | */ 117 | protected function handleRevert() 118 | { 119 | $attachments = $this->getAttachments(true); 120 | 121 | if ($attachments->isEmpty()) { 122 | $this->components->info('All media attachments already have original slugs.'); 123 | 124 | return; 125 | } 126 | 127 | if (! $this->components->confirm("Are you sure you want to revert {$attachments->count()} media attachments?")) { 128 | return; 129 | } 130 | 131 | $timer = microtime(true); 132 | 133 | $attachments->each(function ($attachment) { 134 | $filename = basename($attachment['url']); 135 | 136 | if (! Str::isUuid($attachment['name'])) { 137 | $this->components->warn("{$filename} ({$attachment['name']}) is not a UUID."); 138 | 139 | return; 140 | } 141 | 142 | remove_filter('wp_unique_post_slug', [$this->package, 'randomizeAttachmentSlug']); 143 | 144 | $slug = $attachment['title'] ?: Str::beforeLast($filename, '.'); 145 | 146 | wp_update_post([ 147 | 'ID' => $attachment['id'], 148 | 'post_name' => sanitize_title($slug), 149 | ]); 150 | 151 | $slug = get_post($attachment['id'])->post_name; 152 | 153 | $this->components->info("{$filename} ({$attachment['name']}) has been reverted to {$slug}."); 154 | 155 | $this->count++; 156 | }); 157 | 158 | $timer = round(microtime(true) - $timer, 2); 159 | 160 | $this->newLine(); 161 | $this->components->info("Successfully reverted {$this->count} media attachments in {$timer} second(s)."); 162 | } 163 | 164 | /** 165 | * Get existing attachments that are not UUID's. 166 | * 167 | * @return \Illuminate\Support\Collection 168 | */ 169 | protected function getAttachments(bool $uuids = false) 170 | { 171 | global $wpdb; 172 | 173 | $uuids = $uuids ? 'RLIKE' : 'NOT RLIKE'; 174 | 175 | $attachments = $wpdb->get_results( 176 | "SELECT * FROM {$wpdb->prefix}posts WHERE post_type = 'attachment' AND post_name {$uuids} {$this->pattern}" 177 | ); 178 | 179 | return collect($attachments)->map(fn ($attachment) => [ 180 | 'id' => $attachment->ID, 181 | 'name' => $attachment->post_name, 182 | 'title' => $attachment->post_title, 183 | 'url' => $attachment->guid, 184 | ]); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/DisableMediaPages.php: -------------------------------------------------------------------------------- 1 | set_404(); 66 | status_header(404); 67 | nocache_headers(); 68 | } 69 | 70 | /** 71 | * 404 requests to canonical attachment pages. 72 | * 73 | * @param string $redirect_url 74 | * @param string $requested_url 75 | * @return void 76 | */ 77 | public function disableCanonicalAttachmentPages($redirect_url, $requested_url) 78 | { 79 | if (! is_attachment()) { 80 | return $redirect_url; 81 | } 82 | 83 | global $wp_query; 84 | 85 | $wp_query->set_404(); 86 | status_header(404); 87 | nocache_headers(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/DisableMediaPagesServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('Log1x/DisableMediaPages', function () { 18 | return new DisableMediaPages(); 19 | }); 20 | } 21 | 22 | /** 23 | * Bootstrap any application services. 24 | * 25 | * @return void 26 | */ 27 | public function boot() 28 | { 29 | $this->commands([ 30 | MediaGenerateSlugsCommand::class, 31 | ]); 32 | 33 | $this->app->make('Log1x/DisableMediaPages'); 34 | } 35 | } 36 | --------------------------------------------------------------------------------