PluginProbe
MM Title Manager — Hide Page and Post Titles / 1.10
MM Title Manager — Hide Page and Post Titles v1.10
1.12 1.11 trunk 1.10 1.10.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.9 1.9.1
← All changes | hide-titles.php +381 -532 trunk1.10 View file →
@@ -1,532 +1,381 @@
1 -<?php
2 -/**
3 - * Plugin Name: MM Title Manager
4 - * Plugin URI: https://wordpress.org/plugins/hide-titles/
5 - * Description: Control the visibility of titles on single posts and pages, globally or per item. Navigation menus always keep their labels.
6 - * Version: 1.12
7 - * Requires at least: 4.4
8 - * Requires PHP: 7.4
9 - * Tested up to: 7.1
10 - * Author: Mehraz Morshed
11 - * Author URI: https://profiles.wordpress.org/mehrazmorshed/
12 - * License: GPL v2 or later
13 - * License URI: https://www.gnu.org/licenses/gpl-2.0.html
14 - * Text Domain: hide-titles
15 - * Domain Path: /languages
16 - *
17 - * @package Hide_Titles
18 - */
19 -
20 -/**
21 - * MM Title Manager is free software: you can redistribute it and/or modify
22 - * it under the terms of the GNU General Public License as published by the
23 - * Free Software Foundation, either version 2 of the License, or (at your
24 - * option) any later version.
25 - *
26 - * MM Title Manager is distributed in the hope that it will be useful, but
27 - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
28 - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
29 - * for more details.
30 - *
31 - * You should have received a copy of the GNU General Public License along
32 - * with this program. If not, see <https://www.gnu.org/licenses/>.
33 - */
34 -
35 -// Exit if accessed directly.
36 -defined( 'ABSPATH' ) || exit;
37 -
38 -/**
39 - * Plugin version. Single source of truth for cache busting and enqueues.
40 - */
41 -define( 'HIDE_TITLES_VERSION', '1.10.1' );
42 -
43 -/**
44 - * Option name that stores the global behavior ('nothing' or 'all').
45 - */
46 -define( 'HIDE_TITLES_OPTION', 'hide-titles-option' );
47 -
48 -/**
49 - * Post meta key that stores the per-post/page toggle ('on' or absent).
50 - */
51 -define( 'HIDE_TITLES_META_KEY', '_hide_title' );
52 -
53 -/**
54 - * Load the plugin text domain for translations.
55 - *
56 - * @since 1.0.0
57 - * @return void
58 - */
59 -function hide_titles_load_textdomain() {
60 - load_plugin_textdomain( 'hide-titles', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
61 -}
62 -add_action( 'init', 'hide_titles_load_textdomain' );
63 -
64 -/**
65 - * Register the top-level admin menu page.
66 - *
67 - * @since 1.2.0
68 - * @return void
69 - */
70 -function hide_titles_option_page() {
71 - add_menu_page(
72 - __( 'MM Title Manager', 'hide-titles' ),
73 - __( 'MM Title Manager', 'hide-titles' ),
74 - 'manage_options',
75 - 'hide-titles',
76 - 'hide_titles_create_page',
77 - 'dashicons-heading',
78 - 101
79 - );
80 -}
81 -add_action( 'admin_menu', 'hide_titles_option_page' );
82 -
83 -/**
84 - * Enqueue admin styles on the settings screen and the post/page editors.
85 - *
86 - * @since 1.2.0
87 - * @param string $hook_suffix The current admin page hook suffix.
88 - * @return void
89 - */
90 -function hide_titles_style_settings( $hook_suffix ) {
91 - unset( $hook_suffix );
92 -
93 - $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
94 - if ( ! $screen ) {
95 - return;
96 - }
97 -
98 - $screens = array( 'toplevel_page_hide-titles', 'post', 'page' );
99 - if ( ! in_array( $screen->id, $screens, true ) ) {
100 - return;
101 - }
102 -
103 - wp_enqueue_style(
104 - 'hide-titles-admin',
105 - plugins_url( 'css/hide-titles-settings.css', __FILE__ ),
106 - array(),
107 - HIDE_TITLES_VERSION
108 - );
109 -}
110 -add_action( 'admin_enqueue_scripts', 'hide_titles_style_settings' );
111 -
112 -/**
113 - * Render the settings page.
114 - *
115 - * @since 1.2.0
116 - * @return void
117 - */
118 -function hide_titles_create_page() {
119 - $current = get_option( HIDE_TITLES_OPTION, 'nothing' );
120 - ?>
121 - <div class="hide_titles_main">
122 - <div class="hide_titles_body hide_titles_common">
123 - <header class="ht-mm-hero">
124 - <p class="ht-mm-hero__eyebrow"><?php esc_html_e( 'MM Title Manager', 'hide-titles' ); ?></p>
125 - <h1 class="ht-mm-hero__title" id="page-title"><?php esc_html_e( 'Title visibility', 'hide-titles' ); ?></h1>
126 - <p class="ht-mm-hero__lead"><?php esc_html_e( 'Control titles on single posts and pages. Navigation menus always keep their labels.', 'hide-titles' ); ?></p>
127 - </header>
128 -
129 - <form class="ht-mm-form" action="options.php" method="post">
130 - <?php wp_nonce_field( 'update-options' ); ?>
131 -
132 - <fieldset class="ht-mm-fieldset">
133 - <legend class="ht-mm-fieldset__legend"><?php esc_html_e( 'Global behavior', 'hide-titles' ); ?></legend>
134 - <p class="ht-mm-fieldset__hint"><?php esc_html_e( 'Applies site-wide unless you override per post or page in the editor.', 'hide-titles' ); ?></p>
135 -
136 - <div class="ht-mm-options">
137 - <label class="ht-mm-card">
138 - <input type="radio" name="hide-titles-option" id="hide-titles-option-nothing" value="nothing" <?php checked( $current, 'nothing' ); ?> />
139 - <span class="ht-mm-card__icon dashicons dashicons-visibility" aria-hidden="true"></span>
140 - <span class="ht-mm-card__text">
141 - <strong class="ht-mm-card__title"><?php esc_html_e( 'Show all titles', 'hide-titles' ); ?></strong>
142 - <span class="ht-mm-card__desc"><?php esc_html_e( 'Default WordPress title output everywhere.', 'hide-titles' ); ?></span>
143 - </span>
144 - </label>
145 -
146 - <label class="ht-mm-card">
147 - <input type="radio" name="hide-titles-option" id="hide-titles-option-all" value="all" <?php checked( $current, 'all' ); ?> />
148 - <span class="ht-mm-card__icon dashicons dashicons-no-alt" aria-hidden="true"></span>
149 - <span class="ht-mm-card__text">
150 - <strong class="ht-mm-card__title"><?php esc_html_e( 'Hide all titles', 'hide-titles' ); ?></strong>
151 - <span class="ht-mm-card__desc"><?php esc_html_e( 'Removes the title on single posts and pages; menus still show link text.', 'hide-titles' ); ?></span>
152 - </span>
153 - </label>
154 - </div>
155 - </fieldset>
156 -
157 - <input type="hidden" name="action" value="update" />
158 - <input type="hidden" name="page_options" value="hide-titles-option" />
159 -
160 - <p class="ht-mm-actions">
161 - <button type="submit" name="submit" class="button button-primary button-hero ht-mm-submit"><?php esc_html_e( 'Save changes', 'hide-titles' ); ?></button>
162 - </p>
163 - </form>
164 -
165 - <section class="ht-mm-tip" aria-labelledby="ht-mm-per-post-heading">
166 - <h2 id="ht-mm-per-post-heading" class="ht-mm-tip__title"><?php esc_html_e( 'Per post or page', 'hide-titles' ); ?></h2>
167 - <p class="ht-mm-tip__text"><?php esc_html_e( 'Edit any post or page and use the MM Title Manager box in the sidebar to hide only that item’s title. Menu labels stay visible.', 'hide-titles' ); ?></p>
168 - <figure class="ht-mm-tip__figure">
169 - <img class="screenshot" src="<?php echo esc_url( plugin_dir_url( __FILE__ ) . 'img/single.png' ); ?>" alt="" />
170 - </figure>
171 - </section>
172 - </div>
173 -
174 - <div class="hide_titles_aside hide_titles_common">
175 - <h2 class="aside-title"><?php esc_html_e( 'About Plugin Author', 'hide-titles' ); ?></h2>
176 - <div class="author-card">
177 - <a class="link" href="https://profiles.wordpress.org/mehrazmorshed/" target="_blank" rel="noopener noreferrer">
178 - <img class="center" src="<?php echo esc_url( plugin_dir_url( __FILE__ ) . 'img/author.png' ); ?>" width="128" height="128" alt="" />
179 - <span class="author-title author-title--name"><?php esc_html_e( 'Mehraz Morshed', 'hide-titles' ); ?></span>
180 - <span class="author-title author-title--role"><?php esc_html_e( 'WordPress Developer', 'hide-titles' ); ?></span>
181 - </a>
182 - <nav class="author-socials" aria-label="<?php esc_attr_e( 'Author social links', 'hide-titles' ); ?>">
183 - <a class="link" href="https://www.facebook.com/mehrazmorshed" target="_blank" rel="noopener noreferrer"><span class="dashicons dashicons-facebook" aria-hidden="true"></span><span class="screen-reader-text"><?php esc_html_e( 'Facebook', 'hide-titles' ); ?></span></a>
184 - <a class="link" href="https://twitter.com/mehrazmorshed" target="_blank" rel="noopener noreferrer"><span class="dashicons dashicons-twitter" aria-hidden="true"></span><span class="screen-reader-text"><?php esc_html_e( 'Twitter', 'hide-titles' ); ?></span></a>
185 - <a class="link" href="https://www.linkedin.com/in/mehrazmorshed" target="_blank" rel="noopener noreferrer"><span class="dashicons dashicons-linkedin" aria-hidden="true"></span><span class="screen-reader-text"><?php esc_html_e( 'LinkedIn', 'hide-titles' ); ?></span></a>
186 - <a class="link" href="https://www.youtube.com/@mehrazmorshed" target="_blank" rel="noopener noreferrer"><span class="dashicons dashicons-youtube" aria-hidden="true"></span><span class="screen-reader-text"><?php esc_html_e( 'YouTube', 'hide-titles' ); ?></span></a>
187 - </nav>
188 - </div>
189 -
190 - <h3 class="aside-title"><?php esc_html_e( 'Other Useful Plugins', 'hide-titles' ); ?></h3>
191 - <div class="author-card">
192 - <a class="link" href="https://wordpress.org/plugins/turn-off-comments/" target="_blank" rel="noopener noreferrer">
193 - <span class="dashicons dashicons-admin-plugins" aria-hidden="true"></span> <b><?php esc_html_e( 'Disable Comments', 'hide-titles' ); ?></b>
194 - </a>
195 - <hr />
196 - <a class="link" href="https://wordpress.org/plugins/hide-admin-navbar/" target="_blank" rel="noopener noreferrer">
197 - <span class="dashicons dashicons-admin-plugins" aria-hidden="true"></span> <b><?php esc_html_e( 'Hide Admin Toolbar', 'hide-titles' ); ?></b>
198 - </a>
199 - <hr />
200 - <a class="link" href="https://wordpress.org/plugins/about-post-author/" target="_blank" rel="noopener noreferrer">
201 - <span class="dashicons dashicons-admin-plugins" aria-hidden="true"></span> <b><?php esc_html_e( 'Simple Author Box', 'hide-titles' ); ?></b>
202 - </a>
203 - </div>
204 -
205 - <h3 class="aside-title"><?php esc_html_e( 'Support', 'hide-titles' ); ?></h3>
206 - <a class="link" href="https://www.buymeacoffee.com/mehrazmorshed" target="_blank" rel="noopener noreferrer">
207 - <button type="button" class="button button-primary btn"><?php esc_html_e( 'Donate to this plugin', 'hide-titles' ); ?></button>
208 - </a>
209 - <p class="ht-mm-or"><span><?php esc_html_e( 'or', 'hide-titles' ); ?></span></p>
210 - <a class="link" href="https://wordpress.org/support/plugin/hide-titles/reviews/#new-post" target="_blank" rel="noopener noreferrer">
211 - <button type="button" class="button button-secondary btn"><?php esc_html_e( 'Leave a review', 'hide-titles' ); ?></button>
212 - </a>
213 - </div>
214 - </div>
215 - <?php
216 -}
217 -
218 -/**
219 - * Filter post and page titles to hide them when configured.
220 - *
221 - * Handles both the global "hide all" setting and the per-post toggle in a
222 - * single, well-guarded callback:
223 - *
224 - * - Titles are never altered inside the admin.
225 - * - The per-post toggle hides that item's title wherever it renders.
226 - * - The global mode only blanks the main title of the current singular
227 - * front-end view, so archives, widgets, and secondary loops keep their
228 - * titles. Navigation menu labels are restored separately via
229 - * {@see hide_titles_restore_nav_menu_title()}.
230 - *
231 - * @since 1.10.1 Merged the previous global and per-post handlers and added
232 - * the admin and singular-context guards (returns an empty
233 - * string instead of boolean false).
234 - *
235 - * @param string $title The post title.
236 - * @param int|null $post_id Optional. The post ID. Default null.
237 - * @return string The (possibly emptied) title.
238 - */
239 -function hide_titles_filter_the_title( $title, $post_id = null ) {
240 - if ( is_admin() ) {
241 - return $title;
242 - }
243 -
244 - $post_id = (int) $post_id;
245 - if ( $post_id < 1 ) {
246 - return $title;
247 - }
248 -
249 - // Per-post/page override: applies wherever that item's title renders.
250 - if ( 'on' === get_post_meta( $post_id, HIDE_TITLES_META_KEY, true ) ) {
251 - return '';
252 - }
253 -
254 - // Global mode: only blank the main title of the current singular view.
255 - if ( 'all' === get_option( HIDE_TITLES_OPTION, 'nothing' )
256 - && is_singular()
257 - && get_queried_object_id() === $post_id
258 - ) {
259 - return '';
260 - }
261 -
262 - return $title;
263 -}
264 -add_filter( 'the_title', 'hide_titles_filter_the_title', 10, 2 );
265 -
266 -/**
267 - * Restore menu link text when a title is hidden via the_title.
268 - *
269 - * WordPress runs post titles through `the_title` when building menu items
270 - * (see wp_setup_nav_menu_item). Without this, a per-post hidden title would
271 - * become blank in navigation menus. We keep the stored navigation label, or
272 - * the raw post title, for display in menus only.
273 - *
274 - * @since 1.10.0
275 - *
276 - * @param string $title The menu item's title.
277 - * @param object $menu_item The menu item object.
278 - * @param stdClass $args An object of wp_nav_menu() arguments.
279 - * @param int $depth Depth of menu item.
280 - * @return string
281 - */
282 -function hide_titles_restore_nav_menu_title( $title, $menu_item, $args, $depth ) {
283 - unset( $args, $depth );
284 -
285 - if ( ! is_object( $menu_item ) || 'post_type' !== $menu_item->type || empty( $menu_item->object_id ) ) {
286 - return $title;
287 - }
288 -
289 - $object_id = (int) $menu_item->object_id;
290 - if ( $object_id < 1 ) {
291 - return $title;
292 - }
293 -
294 - $hide_globally = ( 'all' === get_option( HIDE_TITLES_OPTION, 'nothing' ) );
295 - $hide_per_post = ( 'on' === get_post_meta( $object_id, HIDE_TITLES_META_KEY, true ) );
296 -
297 - if ( ! $hide_globally && ! $hide_per_post ) {
298 - return $title;
299 - }
300 -
301 - if ( isset( $menu_item->post_title ) && '' !== $menu_item->post_title ) {
302 - return $menu_item->post_title;
303 - }
304 -
305 - $raw = get_post_field( 'post_title', $object_id, 'raw' );
306 -
307 - return ( is_string( $raw ) && '' !== $raw ) ? $raw : $title;
308 -}
309 -add_filter( 'nav_menu_item_title', 'hide_titles_restore_nav_menu_title', 10, 4 );
310 -
311 -/**
312 - * Register the editor meta box for the per-post toggle.
313 - *
314 - * @since 1.6.0
315 - * @return void
316 - */
317 -function hide_titles_meta_box() {
318 - add_meta_box(
319 - 'hide-title-meta-box',
320 - __( 'MM Title Manager', 'hide-titles' ),
321 - 'hide_titles_meta_box_callback',
322 - array( 'post', 'page' ),
323 - 'side',
324 - 'default'
325 - );
326 -}
327 -add_action( 'add_meta_boxes', 'hide_titles_meta_box' );
328 -
329 -/**
330 - * Render the meta box.
331 - *
332 - * @since 1.6.0
333 - * @param WP_Post $post The post being edited.
334 - * @return void
335 - */
336 -function hide_titles_meta_box_callback( $post ) {
337 - $value = get_post_meta( $post->ID, HIDE_TITLES_META_KEY, true );
338 - wp_nonce_field( 'hide_titles_meta_save', 'hide_titles_meta_nonce' );
339 - ?>
340 - <div class="ht-mm-meta">
341 - <label class="ht-mm-meta__toggle" for="hide-titles-checkbox">
342 - <input type="checkbox" id="hide-titles-checkbox" name="hide_titles_checkbox" <?php checked( $value, 'on' ); ?> />
343 - <span class="ht-mm-meta__label"><?php esc_html_e( 'Hide title on this page', 'hide-titles' ); ?></span>
344 - </label>
345 - <p class="ht-mm-meta__hint"><?php esc_html_e( 'Hides the title in the content area only. Navigation menus and the admin list keep their titles.', 'hide-titles' ); ?></p>
346 - <p class="ht-mm-meta__review"><a href="https://wordpress.org/support/plugin/hide-titles/reviews/#new-post" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Leave a review', 'hide-titles' ); ?></a></p>
347 - </div>
348 - <?php
349 -}
350 -
351 -/**
352 - * Save the meta box value.
353 - *
354 - * @since 1.6.0
355 - * @param int $post_id The post ID being saved.
356 - * @return void
357 - */
358 -function hide_titles_save_meta( $post_id ) {
359 - if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
360 - return;
361 - }
362 -
363 - if ( ! isset( $_POST['hide_titles_meta_nonce'] )
364 - || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['hide_titles_meta_nonce'] ) ), 'hide_titles_meta_save' )
365 - ) {
366 - return;
367 - }
368 -
369 - if ( ! current_user_can( 'edit_post', $post_id ) ) {
370 - return;
371 - }
372 -
373 - if ( isset( $_POST['hide_titles_checkbox'] ) ) {
374 - update_post_meta( $post_id, HIDE_TITLES_META_KEY, 'on' );
375 - } else {
376 - delete_post_meta( $post_id, HIDE_TITLES_META_KEY );
377 - }
378 -}
379 -add_action( 'save_post', 'hide_titles_save_meta' );
380 -
381 -/**
382 - * Runs on plugin activation.
383 - *
384 - * Records the installation datetime once and sets a one-time flag used to
385 - * redirect the user to the settings page after activation.
386 - *
387 - * @since 1.10.1 Merged the two former activation callbacks.
388 - * @return void
389 - */
390 -function hide_titles_activate() {
391 - if ( ! get_option( 'hide_titles_installed' ) ) {
392 - update_option( 'hide_titles_installed', current_time( 'mysql' ), false );
393 - }
394 -
395 - add_option( 'hide_titles_plugin_do_activation_redirect', true, '', false );
396 -}
397 -register_activation_hook( __FILE__, 'hide_titles_activate' );
398 -
399 -/**
400 - * Redirect to the settings page once, immediately after activation.
401 - *
402 - * @since 1.2.0
403 - * @return void
404 - */
405 -function hide_titles_plugin_redirect() {
406 - if ( ! get_option( 'hide_titles_plugin_do_activation_redirect', false ) ) {
407 - return;
408 - }
409 -
410 - delete_option( 'hide_titles_plugin_do_activation_redirect' );
411 -
412 - // Do not redirect during bulk (multi) plugin activation.
413 - if ( ! isset( $_GET['active-multi'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only presence check, no data processed.
414 - wp_safe_redirect( admin_url( 'admin.php?page=hide-titles' ) );
415 - exit;
416 - }
417 -}
418 -add_action( 'admin_init', 'hide_titles_plugin_redirect' );
419 -
420 -/**
421 - * Clean up all plugin data on uninstall.
422 - *
423 - * @since 1.10.1 Now removes every option the plugin creates and sweeps the
424 - * per-post meta so no orphaned rows are left behind.
425 - * @return void
426 - */
427 -function hide_titles_uninstall() {
428 - delete_option( 'hide_titles_installed' );
429 - delete_option( HIDE_TITLES_OPTION );
430 - delete_option( 'hide_titles_plugin_do_activation_redirect' );
431 - delete_post_meta_by_key( HIDE_TITLES_META_KEY );
432 -}
433 -register_uninstall_hook( __FILE__, 'hide_titles_uninstall' );
434 -
435 -/**
436 - * Handle the "dismiss" action for the migration notice.
437 - *
438 - * @since 1.10.1
439 - * @return void
440 - */
441 -function hide_titles_handle_migration_dismiss() {
442 - if ( ! isset( $_GET['hide_titles_dismiss_migration'], $_GET['_wpnonce'] ) ) {
443 - return;
444 - }
445 -
446 - if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'hide_titles_dismiss_migration' ) ) {
447 - return;
448 - }
449 -
450 - $user_id = get_current_user_id();
451 - if ( $user_id ) {
452 - update_user_meta( $user_id, 'hide_titles_migration_dismissed', 1 );
453 - }
454 -
455 - wp_safe_redirect( remove_query_arg( array( 'hide_titles_dismiss_migration', '_wpnonce' ) ) );
456 - exit;
457 -}
458 -add_action( 'admin_init', 'hide_titles_handle_migration_dismiss' );
459 -
460 -/**
461 - * Show a migration notice to legacy installs only.
462 - *
463 - * The notice is shown only when all of the following are true: the successor
464 - * plugin is not active, the user has not dismissed it, and the install
465 - * predates 30 October 2025. Fresh installs are never nagged.
466 - *
467 - * @since 1.10.1 Added the dismissal check, guarded is_plugin_active(), moved
468 - * to a warning notice, and stopped showing on fresh installs.
469 - * @return void
470 - */
471 -function hide_titles_show_migration_notice() {
472 - $user_id = get_current_user_id();
473 - if ( $user_id && get_user_meta( $user_id, 'hide_titles_migration_dismissed', true ) ) {
474 - return;
475 - }
476 -
477 - if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'daisy-titles/daisy-titles.php' ) ) {
478 - return;
479 - }
480 -
481 - // Only nudge installs that predate the cutoff. Fresh/unknown installs are left alone.
482 - $install_date = get_option( 'hide_titles_installed' );
483 - if ( ! $install_date || strtotime( $install_date ) >= strtotime( '2025-10-30' ) ) {
484 - return;
485 - }
486 -
487 - $activate_url = '';
488 - if ( file_exists( WP_PLUGIN_DIR . '/daisy-titles/daisy-titles.php' ) ) {
489 - $activate_url = wp_nonce_url(
490 - add_query_arg(
491 - array(
492 - 'action' => 'activate',
493 - 'plugin' => 'daisy-titles/daisy-titles.php',
494 - ),
495 - admin_url( 'plugins.php' )
496 - ),
497 - 'activate-plugin_daisy-titles/daisy-titles.php'
498 - );
499 - } else {
500 - $activate_url = wp_nonce_url(
501 - add_query_arg(
502 - array(
503 - 'action' => 'install-plugin',
504 - 'plugin' => 'daisy-titles',
505 - ),
506 - admin_url( 'update.php' )
507 - ),
508 - 'install-plugin_daisy-titles'
509 - );
510 - }
511 -
512 - $is_installed = ( '' !== $activate_url && false !== strpos( $activate_url, 'action=activate' ) );
513 - $button_label = $is_installed
514 - ? __( 'Activate Daisy Titles now', 'hide-titles' )
515 - : __( 'Migrate to Daisy Titles now', 'hide-titles' );
516 -
517 - $dismiss_url = wp_nonce_url(
518 - add_query_arg( 'hide_titles_dismiss_migration', '1' ),
519 - 'hide_titles_dismiss_migration'
520 - );
521 - ?>
522 - <div class="notice notice-warning">
523 - <h4><?php esc_html_e( 'Important notice about MM Title Manager', 'hide-titles' ); ?></h4>
524 - <p><?php esc_html_e( 'This plugin is no longer actively maintained. Please migrate to our new, improved plugin “Daisy Titles” for continued support, new features, and future updates.', 'hide-titles' ); ?></p>
525 - <p>
526 - <a href="<?php echo esc_url( $activate_url ); ?>" class="button button-primary"><?php echo esc_html( $button_label ); ?></a>
527 - <a href="<?php echo esc_url( $dismiss_url ); ?>" class="button button-link"><?php esc_html_e( 'Dismiss', 'hide-titles' ); ?></a>
528 - </p>
529 - </div>
530 - <?php
531 -}
532 -add_action( 'admin_notices', 'hide_titles_show_migration_notice' );
1 +<?php
2 +
3 +/**
4 + * Plugin Name: MM Title Manager
5 + * Plugin URI: https://wordpress.org/plugins/hide-titles/
6 + * Description: Remove Titles from Posts and Single Pages on WordPress.
7 + * Version: 1.10
8 + * Requires at least: 4.4
9 + * Requires PHP: 5.6
10 + * Tested up to: 7.0
11 + * Author: Mehraz Morshed
12 + * Author URI: https://profiles.wordpress.org/mehrazmorshed/
13 + * License: GPL v2 or later
14 + * License URI: https://www.gnu.org/licenses/gpl-2.0.html
15 + * Text Domain: hide-titles
16 + * Domain Path: /languages
17 + */
18 +
19 +/**
20 + * Hide Titles is free software: you can redistribute it and/or
21 + * modify it under the terms of the GNU General Public License
22 + * as published by the Free Software Foundation, either version 3
23 + * of the License, or (at your option) any later version.
24 + *
25 + * Hide Titles is distributed in the hope that it will be useful,
26 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 + * GNU General Public License for more details.
29 + *
30 + * You should have received a copy of the GNU General Public License
31 + * along with this program. If not, see <https://www.gnu.org/licenses/>.
32 + */
33 +
34 +// Exit if accessed directly
35 +if ( ! defined( 'ABSPATH' ) ) {
36 + exit;
37 +}
38 +
39 +// Load the text-domain
40 +function hide_titles_load_textdomain() {
41 + load_plugin_textdomain( 'hide-titles', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
42 +}
43 +add_action( 'init', 'hide_titles_load_textdomain' );
44 +
45 +function hide_titles_option_page() {
46 +
47 + add_menu_page( __( 'MM Title Manager', 'hide-titles' ), __( 'MM Title Manager', 'hide-titles' ), 'manage_options', 'hide-titles', 'hide_titles_create_page', 'dashicons-heading', 101 );
48 +}
49 +add_action( 'admin_menu', 'hide_titles_option_page' );
50 +
51 +function hide_titles_style_settings( $hook_suffix ) {
52 +
53 + $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
54 + if ( ! $screen ) {
55 + return;
56 + }
57 +
58 + $screens = array( 'toplevel_page_hide-titles', 'post', 'page' );
59 + if ( ! in_array( $screen->id, $screens, true ) ) {
60 + return;
61 + }
62 +
63 + wp_enqueue_style( 'hide-titles-admin', plugins_url( 'css/hide-titles-settings.css', __FILE__ ), array(), '1.9.2' );
64 +}
65 +add_action( 'admin_enqueue_scripts', 'hide_titles_style_settings' );
66 +
67 +function hide_titles_create_page() {
68 + $current = get_option( 'hide-titles-option', 'nothing' );
69 + ?>
70 + <div class="hide_titles_main">
71 + <div class="hide_titles_body hide_titles_common">
72 + <header class="ht-mm-hero">
73 + <p class="ht-mm-hero__eyebrow"><?php esc_html_e( 'MM Title Manager', 'hide-titles' ); ?></p>
74 + <h1 class="ht-mm-hero__title" id="page-title"><?php esc_html_e( 'Title visibility', 'hide-titles' ); ?></h1>
75 + <p class="ht-mm-hero__lead"><?php esc_html_e( 'Control titles on single posts and pages. Navigation menus always keep their labels.', 'hide-titles' ); ?></p>
76 + </header>
77 +
78 + <form class="ht-mm-form" action="options.php" method="post">
79 + <?php wp_nonce_field( 'update-options' ); ?>
80 +
81 + <fieldset class="ht-mm-fieldset">
82 + <legend class="ht-mm-fieldset__legend"><?php esc_html_e( 'Global behavior', 'hide-titles' ); ?></legend>
83 + <p class="ht-mm-fieldset__hint"><?php esc_html_e( 'Applies site-wide unless you override per post or page in the editor.', 'hide-titles' ); ?></p>
84 +
85 + <div class="ht-mm-options">
86 + <label class="ht-mm-card">
87 + <input type="radio" name="hide-titles-option" id="hide-titles-option-nothing" value="nothing" <?php checked( $current, 'nothing' ); ?>>
88 + <span class="ht-mm-card__icon dashicons dashicons-visibility" aria-hidden="true"></span>
89 + <span class="ht-mm-card__text">
90 + <strong class="ht-mm-card__title"><?php esc_html_e( 'Show all titles', 'hide-titles' ); ?></strong>
91 + <span class="ht-mm-card__desc"><?php esc_html_e( 'Default WordPress title output everywhere.', 'hide-titles' ); ?></span>
92 + </span>
93 + </label>
94 +
95 + <label class="ht-mm-card">
96 + <input type="radio" name="hide-titles-option" id="hide-titles-option-all" value="all" <?php checked( $current, 'all' ); ?>>
97 + <span class="ht-mm-card__icon dashicons dashicons-no-alt" aria-hidden="true"></span>
98 + <span class="ht-mm-card__text">
99 + <strong class="ht-mm-card__title"><?php esc_html_e( 'Hide all titles', 'hide-titles' ); ?></strong>
100 + <span class="ht-mm-card__desc"><?php esc_html_e( 'Removes titles in templates; menus still show link text.', 'hide-titles' ); ?></span>
101 + </span>
102 + </label>
103 + </div>
104 + </fieldset>
105 +
106 + <input type="hidden" name="action" value="update">
107 + <input type="hidden" name="page_options" value="hide-titles-option">
108 +
109 + <p class="ht-mm-actions">
110 + <button type="submit" name="submit" class="button button-primary button-hero ht-mm-submit"><?php esc_html_e( 'Save changes', 'hide-titles' ); ?></button>
111 + </p>
112 + </form>
113 +
114 + <section class="ht-mm-tip" aria-labelledby="ht-mm-per-post-heading">
115 + <h2 id="ht-mm-per-post-heading" class="ht-mm-tip__title"><?php esc_html_e( 'Per post or page', 'hide-titles' ); ?></h2>
116 + <p class="ht-mm-tip__text"><?php esc_html_e( 'Edit any post or page and use the Hide Titles box in the sidebar to hide only that item’s title. Menu labels stay visible.', 'hide-titles' ); ?></p>
117 + <figure class="ht-mm-tip__figure">
118 + <img class="screenshot" src="<?php echo esc_url( plugin_dir_url( __FILE__ ) . 'img/single.png' ); ?>" alt="">
119 + </figure>
120 + </section>
121 + </div>
122 + <div class="hide_titles_aside hide_titles_common">
123 + <!-- about plugin author -->
124 + <h2 class="aside-title"><?php esc_html_e( 'About Plugin Author', 'hide-titles' ); ?></h2>
125 + <div class="author-card">
126 + <a class="link" href="https://profiles.wordpress.org/mehrazmorshed/" target="_blank">
127 + <img class="center" src="<?php echo esc_url( plugin_dir_url( __FILE__ ) . 'img/author.png' ); ?>" width="128" height="128" alt="">
128 + <h3 class="author-title"><?php esc_html_e( 'Mehraz Morshed', 'hide-titles' ); ?></h3>
129 + <h4 class="author-title"><?php esc_html_e( 'WordPress Developer', 'hide-titles' ); ?></h4>
130 + </a>
131 + <h1 class="author-title">
132 + <a class="link" href="https://www.facebook.com/mehrazmorshed" target="_blank"><span class="dashicons dashicons-facebook"></span></a>
133 + <a class="link" href="https://twitter.com/mehrazmorshed" target="_blank"><span class="dashicons dashicons-twitter"></span></a>
134 + <a class="link" href="https://www.linkedin.com/in/mehrazmorshed" target="_blank"><span class="dashicons dashicons-linkedin"></span></a>
135 + <a class="link" href="https://www.youtube.com/@mehrazmorshed" target="_blank"><span class="dashicons dashicons-youtube"></span></a>
136 + </h1>
137 + </div>
138 + <!-- other useful plugins -->
139 + <h3 class="aside-title"><?php esc_html_e( 'Other Useful Plugins', 'hide-titles' ); ?></h3>
140 + <div class="author-card">
141 + <a class="link" href="https://wordpress.org/plugins/turn-off-comments/" target="_blank">
142 + <span class="dashicons dashicons-admin-plugins"></span> <b><?php _e( 'Disable Comments', 'hide-titles' ) ?></b>
143 + </a>
144 + <hr>
145 + <a class="link" href="https://wordpress.org/plugins/hide-admin-navbar/" target="_blank">
146 + <span class="dashicons dashicons-admin-plugins"></span> <b><?php _e( 'Hide Admin Toolbar', 'hide-titles' ) ?></b>
147 + </a>
148 + <hr>
149 + <a class="link" href="https://wordpress.org/plugins/about-post-author/" target="_blank">
150 + <span class="dashicons dashicons-admin-plugins"></span> <b><?php _e( 'Simple Author Box', 'hide-titles' ) ?></b>
151 + </a>
152 + </div>
153 + <!-- donate to this plugin -->
154 + <h3 class="aside-title"><?php esc_html_e( 'Support', 'hide-titles' ); ?></h3>
155 + <a class="link" href="https://www.buymeacoffee.com/mehrazmorshed" target="_blank" rel="noopener noreferrer">
156 + <button type="button" class="button button-primary btn"><?php esc_html_e( 'Donate to this plugin', 'hide-titles' ); ?></button>
157 + </a>
158 + <p class="ht-mm-or"><span><?php esc_html_e( 'or', 'hide-titles' ); ?></span></p>
159 + <a class="link" href="https://wordpress.org/support/plugin/hide-titles/reviews/#new-post" target="_blank" rel="noopener noreferrer">
160 + <button type="button" class="button button-secondary btn"><?php esc_html_e( 'Leave a review', 'hide-titles' ); ?></button>
161 + </a>
162 + </div>
163 + </div>
164 +
165 + <?php
166 +}
167 +
168 +if( get_option( 'hide-titles-option' ) == 'all' ) {
169 +
170 + function hide_titles() {
171 +
172 + return false;
173 + }
174 + add_filter('the_title', 'hide_titles');
175 +}
176 +
177 +function hide_titles_plugin_activation() {
178 +
179 + add_option( 'hide_titles_plugin_do_activation_redirect', true );
180 +}
181 +register_activation_hook( __FILE__, 'hide_titles_plugin_activation' );
182 +
183 +function hide_titles_plugin_redirect() {
184 +
185 + if( get_option( 'hide_titles_plugin_do_activation_redirect', false ) ) {
186 +
187 + delete_option( 'hide_titles_plugin_do_activation_redirect' );
188 +
189 + if ( !isset( $_GET['active-multi'] ) ) {
190 +
191 + wp_safe_redirect( admin_url( 'admin.php?page=hide-titles' ) );
192 + exit;
193 + }
194 + }
195 +}
196 +add_action( 'admin_init', 'hide_titles_plugin_redirect' );
197 +/*********************************************************************/
198 +add_action('add_meta_boxes', 'hide_titles_meta_box');
199 +add_action('save_post', 'hide_titles_save_meta');
200 +add_filter( 'the_title', 'hide_titles_filter_title', 10, 2 );
201 +add_filter( 'nav_menu_item_title', 'hide_titles_restore_nav_menu_title', 10, 4 );
202 +
203 +/**
204 + * Restore menu link text when titles are hidden via the_title.
205 + *
206 + * WordPress runs post titles through `the_title` when building menu items
207 + * (see wp_setup_nav_menu_item). Without this, hidden titles become blank
208 + * or “no title” in menus. We keep the stored navigation label or the raw
209 + * post title for display in menus only.
210 + *
211 + * @param string $title The menu item’s title.
212 + * @param object $menu_item The menu item object.
213 + * @param stdClass $args An object of wp_nav_menu() arguments.
214 + * @param int $depth Depth of menu item.
215 + * @return string
216 + */
217 +function hide_titles_restore_nav_menu_title( $title, $menu_item, $args, $depth ) {
218 +
219 + if ( ! is_object( $menu_item ) || 'post_type' !== $menu_item->type || empty( $menu_item->object_id ) ) {
220 + return $title;
221 + }
222 +
223 + $object_id = (int) $menu_item->object_id;
224 + if ( $object_id < 1 ) {
225 + return $title;
226 + }
227 +
228 + $hide_globally = ( get_option( 'hide-titles-option' ) === 'all' );
229 + $hide_per_post = ( get_post_meta( $object_id, '_hide_title', true ) === 'on' );
230 +
231 + if ( ! $hide_globally && ! $hide_per_post ) {
232 + return $title;
233 + }
234 +
235 + if ( isset( $menu_item->post_title ) && '' !== $menu_item->post_title ) {
236 + return $menu_item->post_title;
237 + }
238 +
239 + $raw = get_post_field( 'post_title', $object_id, 'raw' );
240 +
241 + return ( is_string( $raw ) && '' !== $raw ) ? $raw : $title;
242 +}
243 +
244 +// Add meta box to hide title
245 +function hide_titles_meta_box() {
246 + add_meta_box( 'hide-title-meta-box', __( 'MM Title Manager', 'hide-titles' ), 'hide_titles_meta_box_callback', array( 'post', 'page' ), 'side', 'default' );
247 +}
248 +
249 +// Meta box callback function
250 +function hide_titles_meta_box_callback($post) {
251 + $value = get_post_meta($post->ID, '_hide_title', true);
252 + wp_nonce_field( 'hide_titles_meta_save', 'hide_titles_meta_nonce' );
253 + ?>
254 + <div class="ht-mm-meta">
255 + <label class="ht-mm-meta__toggle" for="hide-titles-checkbox">
256 + <input type="checkbox" id="hide-titles-checkbox" name="hide_titles_checkbox" <?php checked( $value, 'on' ); ?> />
257 + <span class="ht-mm-meta__label"><?php esc_html_e( 'Hide title on this page', 'hide-titles' ); ?></span>
258 + </label>
259 + <p class="ht-mm-meta__hint"><?php esc_html_e( 'Hides the title in the content area only. Navigation menus and the admin list keep their titles.', 'hide-titles' ); ?></p>
260 + <p class="ht-mm-meta__review"><a href="https://wordpress.org/support/plugin/hide-titles/reviews/#new-post" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Leave a review', 'hide-titles' ); ?></a></p>
261 + </div>
262 + <?php
263 +}
264 +
265 +// Save meta box data
266 +function hide_titles_save_meta($post_id) {
267 + if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
268 + return;
269 + }
270 + if ( ! isset( $_POST['hide_titles_meta_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['hide_titles_meta_nonce'] ), 'hide_titles_meta_save' ) ) {
271 + return;
272 + }
273 + if ( ! current_user_can( 'edit_post', $post_id ) ) {
274 + return;
275 + }
276 + if ( array_key_exists( 'hide_titles_checkbox', $_POST ) ) {
277 + update_post_meta( $post_id, '_hide_title', 'on' );
278 + } else {
279 + delete_post_meta( $post_id, '_hide_title' );
280 + }
281 +}
282 +
283 +// Filter the title to hide it
284 +function hide_titles_filter_title($title, $id = null) {
285 + if (is_admin() || !$id) {
286 + return $title;
287 + }
288 +
289 + $hide_title = get_post_meta($id, '_hide_title', true);
290 + if ($hide_title === 'on') {
291 + return '';
292 + }
293 +
294 + return $title;
295 +}
296 +
297 +
298 +
299 +
300 +
301 +/**
302 + * Save installation datetime on plugin activation
303 + */
304 +function hide_titles_activate() {
305 + // Check if the installation time is already saved
306 + $installed = get_option('hide_titles_installed');
307 +
308 + if (!$installed) {
309 + // Save current datetime if not already set
310 + update_option('hide_titles_installed', current_time('mysql'));
311 + }
312 +}
313 +register_activation_hook(__FILE__, 'hide_titles_activate');
314 +
315 +/**
316 + * Clean up options on plugin uninstallation
317 + */
318 +function hide_titles_uninstall() {
319 + delete_option('hide_titles_installed');
320 +}
321 +register_uninstall_hook(__FILE__, 'hide_titles_uninstall');
322 +
323 +/**
324 + * Show migration notice for installations before Oct 30, 2025
325 + */
326 +function ht_show_migration_notice() {
327 + // Only show if new plugin is not active
328 + if (is_plugin_active('daisy-titles/daisy-titles.php')) {
329 + return;
330 + }
331 +
332 + // Get installation date
333 + $install_date = get_option('hide_titles_installed');
334 +
335 + // Only show notice if:
336 + // 1. There is NO install date (new installation) OR
337 + // 2. Installation date is BEFORE Oct 30, 2025
338 + if ($install_date && strtotime($install_date) >= strtotime('2025-10-30')) {
339 + return;
340 + }
341 +
342 + // Get install/activate URLs
343 + $install_url = wp_nonce_url(
344 + add_query_arg([
345 + 'action' => 'install-plugin',
346 + 'plugin' => 'daisy-titles'
347 + ], admin_url('update.php')),
348 + 'install-plugin_daisy-titles'
349 + );
350 +
351 + $activate_url = '';
352 + if (file_exists(WP_PLUGIN_DIR . '/daisy-titles/daisy-titles.php')) {
353 + $activate_url = wp_nonce_url(
354 + add_query_arg([
355 + 'action' => 'activate',
356 + 'plugin' => 'daisy-titles/daisy-titles.php'
357 + ], admin_url('plugins.php')),
358 + 'activate-plugin_daisy-titles/daisy-titles.php'
359 + );
360 + }
361 + ?>
362 + <div class="notice notice-error">
363 + <h4><?php esc_html_e('Important Notice About Hide Titles', 'hide-titles'); ?></h4>
364 + <p>
365 + <?php _e('This plugin is no longer maintained. Please migrate to our new improved plugin <b style="color: blue;">"Daisy Titles"</b> for continued support, new features, and future updates.', 'hide-titles'); ?>
366 + </p>
367 + <p>
368 + <?php if ($activate_url) : ?>
369 + <a href="<?php echo esc_url($activate_url); ?>" class="button button-primary">
370 + <?php esc_html_e('Activate Daisy Titles Now', 'hide-titles'); ?>
371 + </a>
372 + <?php else : ?>
373 + <a href="<?php echo esc_url($install_url); ?>" class="button button-primary">
374 + <?php esc_html_e('Migrate to Daisy Titles Now', 'hide-titles'); ?>
375 + </a>
376 + <?php endif; ?>
377 + </p>
378 + </div>
379 + <?php
380 +}
381 +add_action('admin_notices', 'ht_show_migration_notice');