** このページには広告が含まれています。**
ハワイ在住おやじの忘備録

検索結果:「」

ショートコードでカテゴリーを指定した検索を設置してみた

Nextcloudをアップデートする際に、警告への対処を行うためにいつも自分の投稿を探すことが多いので、カテゴリーを限定した検索ができるようにショートコードを作成してみました。
また、検索結果については、タイトルと抜粋した内容しか表示されていなかったので、検索文字を含む段落等のブロックを表示させることにしました。

まずはじめに

設置手順は2つになります。
・ショートコードを新規登録するためにfanction.phpにコードを追記。
・検索結果ページ用のPHPファイルを新規作成

設置ショートコード [[my_custom_search cat_id="XX"]]
cat_idをXXから指定したいカテゴリーIDに変更するだけ (cat_id="16" などに)

完成ごのカテゴリー指定検索結果ページ

初めに投稿記事タイトル、検索文字を含むブロック、最後にその投稿ページのリンクが表示され、複数のページがある場合には上記内容を繰り返しリストされます。

function.phpにコードを追記

早速下記コードをfunction.phpに追記をします。
黄色い部分の<Style>については好きなようにデザイン変更ください。
Cocoonでサイドバー検索ウィジェット設置している場合、通常の検索バーも表示されて分かりずらくなるのでカテゴリ限定検索結果ページでは通常の検索バーは非表示になるようにしています。(青ハイライト部分)

/*************************
指定カテゴリー内検索(ショートコード対応版・検索結果ページ再検索対応・サイドバー検索非表示対応)
**************************/

/**
 * 指定カテゴリ内に検索を限定(mycatsearch=1 の場合)
 */
function my_shortcode_search_limit_category($query) {
    if ($query->is_search() && $query->is_main_query() && !is_admin()) {

        // カテゴリ限定検索(mycatsearch=1)が付いているか?
        if (isset($_GET['mycatsearch']) && $_GET['mycatsearch'] == '1') {

            // ショートコードや検索結果ページからもらった cat_id
            if (!empty($_GET['cat_id'])) {

                // 複数 ID にも対応(例:5,7)
                $cat_ids = array_map('intval', explode(',', $_GET['cat_id']));

                $query->set('category__in', $cat_ids);
            }
        }
    }
}
add_action('pre_get_posts', 'my_shortcode_search_limit_category');



/**
 * カテゴリ限定検索結果ページのフォームだけ置き換え
 */
function my_custom_search_form_override($form) {

    // 条件:カテゴリ限定検索 & search-category.php を使用しているページ
    if (is_search() && isset($_GET['mycatsearch']) && $_GET['mycatsearch'] == '1'
        && !empty($_GET['cat_id'])
        && basename(get_page_template()) === 'search-category.php') {

        $cat_id = esc_attr($_GET['cat_id']);
        $s      = isset($_GET['s']) ? esc_attr($_GET['s']) : '';

        // カテゴリ固定の検索フォームを返す
        $form = '
        <form role="search" method="get" action="' . esc_url(home_url('/')) . '">
            <input type="search" name="s" placeholder="キーワードを入力" value="' . $s . '">
            <input type="hidden" name="mycatsearch" value="1">
            <input type="hidden" name="cat_id" value="' . $cat_id . '">
            <button type="submit">検索</button>
        </form>';
    }

    return $form;
}
add_filter('get_search_form', 'my_custom_search_form_override');



/**
 * ショートコード:カテゴリ限定検索フォーム(中央配置・横並び・レスポンシブ対応)
 * 使用例:[my_custom_search cat_id="5"]
 */
function my_custom_search_shortcode($atts) {

    $atts = shortcode_atts(
        array(
            'cat_id' => '',
        ),
        $atts,
        'my_custom_search'
    );

    $cat_id = esc_attr($atts['cat_id']);

    // フォーム生成
    ob_start(); ?>
    <form class="category-search" role="search" method="get" action="<?php echo esc_url(home_url('/')); ?>">
        <input type="search" name="s" placeholder="キーワードを入力">
        <?php if (!empty($cat_id)) : ?>
            <input type="hidden" name="mycatsearch" value="1">
            <input type="hidden" name="cat_id" value="<?php echo $cat_id; ?>">
        <?php endif; ?>
        <button type="submit">検索</button>
    </form>

    <style>
    /* ------- ショートコード用検索フォーム:中央配置、横並び ------- */
    .category-search {
        display: flex;
        max-width: 400px;          /* フォーム全体の最大幅 */
        width: 100%;
        margin: 0 auto 20px auto;  /* 中央寄せ + 下余白 */
        gap: 5px;                  /* 入力欄とボタンの間隔 */
    }

    /* 入力欄 */
    .category-search input[type="search"] {
        flex: 1;
        padding: 6px 10px;
        font-size: 16px;
        box-sizing: border-box;
    }

    /* ボタン */
    .category-search button {
    background-color: #0073aa;
    color: #fff;
    border: none;
    padding: 8px 16px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    display: inline-block;
    white-space: nowrap;
    }
.category-search button:hover {
    background-color: #005f8d;
}
    /* ------- スマホでは縦並びに変更 ------- */
    @media (max-width: 480px) {
        .category-search {
            flex-direction: column;
        }
        .category-search button {
            width: 100%;
        }
    }
    </style>
    <?php
    return ob_get_clean();
}
add_shortcode('my_custom_search', 'my_custom_search_shortcode');



/**
 * mycatsearch=1 のときだけ search-category.php を強制使用
 */
function my_select_search_template($template) {
    if (isset($_GET['mycatsearch']) && $_GET['mycatsearch'] == '1') {

        $custom_template = locate_template('search-category.php'); // カスタムテンプレート

        if (!empty($custom_template)) {
            return $custom_template;
        }
    }

    return $template; // 通常検索はそのまま
}
add_filter('template_include', 'my_select_search_template');



/**
 * サイドバー検索ウィジェットをカテゴリ限定検索結果ページのみ非表示
 */
function my_hide_sidebar_search_widget($sidebars_widgets) {

    if (is_search() && isset($_GET['mycatsearch']) && $_GET['mycatsearch'] == '1') {

        if (isset($sidebars_widgets['sidebar'])) {
            foreach ($sidebars_widgets['sidebar'] as $key => $widget_id) {

                // 検索ウィジェット(WP_Widget_Search)のみ削除
                if (strpos($widget_id, 'search') !== false) {
                    unset($sidebars_widgets['sidebar'][$key]);
                }
            }
        }
    }

    return $sidebars_widgets;
}
add_filter('sidebars_widgets', 'my_hide_sidebar_search_widget');

検索結果ページ用にsearch-category.phpを作成

通常の検索結果ページとは表示仕方を変更したいため、新たにカテゴリー指定の検索結果ページを作成するためにsearch-category.phpを新たに作成し、子テーマなどに設置します。
黄色い部分の<Style>については好きなようにデザイン変更ください。

<?php get_header(); ?>

<?php
$keyword        = get_search_query();
$highlight      = isset($_GET['hl']) ? $_GET['hl'] == '1' : true; // デフォルトON
$is_cat_limited = isset($_GET['mycatsearch']) && $_GET['mycatsearch'] == '1';
$cat_id         = isset($_GET['cat_id']) ? intval($_GET['cat_id']) : ''; // ★ カテゴリーID受け取り

/**
 * Gutenberg ブロックから検索ワードを含むブロックだけを抽出する
 */
function get_matching_blocks($blocks, $keyword, $highlight = false) {
    $matched = [];

    foreach ($blocks as $block) {

        // ネストされたブロックもチェック
        if (!empty($block['innerBlocks'])) {
            $matched = array_merge($matched, get_matching_blocks($block['innerBlocks'], $keyword, $highlight));
        }

        // ブロックそのものに一致があるか
        $html = render_block($block);
        $plain = strip_tags($html);

        if (stripos($plain, $keyword) !== false) {

            // ハイライト処理
            if ($highlight && !empty($keyword)) {
                $escaped = preg_quote($keyword, '/');
                $html = preg_replace(
                    "/($escaped)/i",
                    '<span class="search-highlight">$1</span>',
                    $html
                );
            }

            $matched[] = $html;
        }
    }

    return $matched;
}
?>

<div class="article">

<h1 class="entry-title">検索結果:「<?php echo esc_html($keyword); ?>」</h1>

<?php if ($is_cat_limited) : ?>

    <!-- ★ カテゴリ限定検索フォーム(検索結果ページ用) -->
    <div class="custom-search-form" style="margin:20px 0;">
        <form role="search" method="get" action="<?php echo home_url('/'); ?>">
            <input type="search" name="s" class="mysearch-input" placeholder="キーワードを入力" value="<?php echo esc_attr($keyword); ?>">

            <!-- カテゴリ限定検索のフラグ -->
            <input type="hidden" name="mycatsearch" value="1">

            <!-- ★ カテゴリーIDも引き継ぐ -->
            <?php if ($cat_id) : ?>
                <input type="hidden" name="cat_id" value="<?php echo esc_attr($cat_id); ?>">
            <?php endif; ?>

            <!-- ハイライト引き継ぎ -->
            <?php if ($highlight): ?>
                <input type="hidden" name="hl" value="1">
            <?php else: ?>
                <input type="hidden" name="hl" value="0">
            <?php endif; ?>

            <button type="submit" class="mysearch-btn">検索</button>
        </form>

        <!-- ハイライト切替 -->
        <div style="margin-top:10px;">
        <?php if ($highlight): ?>
            <a class="button" href="<?php echo esc_url(add_query_arg('hl', '0')); ?>">ハイライト:OFFにする</a>
        <?php else: ?>
            <a class="button" href="<?php echo esc_url(add_query_arg('hl', '1')); ?>">ハイライト:ONにする</a>
        <?php endif; ?>
        </div>
    </div>

<?php endif; ?>

<?php if (have_posts()) : ?>

    <?php while (have_posts()) : the_post(); ?>

        <?php
        $blocks = parse_blocks(get_the_content());
        $matched_blocks = get_matching_blocks($blocks, $keyword, $highlight);
        ?>

        <?php if (!empty($matched_blocks)) : ?>
            <div class="search-result-item">

                <h2 class="search-post-title">
                    <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
                </h2>

                <div class="matched-blocks">
                    <?php foreach ($matched_blocks as $html) : ?>
                        <div class="matched-block"><?php echo $html; ?></div>
                    <?php endforeach; ?>
                </div>

                <p class="search-post-link">
                    <a class="button" href="<?php the_permalink(); ?>">? 元の記事を見る</a>
                </p>

            </div>
        <?php endif; ?>

    <?php endwhile; ?>

<?php else : ?>

    <p>該当する記事がありませんでした。</p>

<?php endif; ?>

</div>

<style>
.matched-block {
    padding: 12px;
    background: #fafafa;
    border-left: 4px solid #ddd;
    margin: 20px 0;
}
.search-highlight {
    background: yellow;
    font-weight: bold;
}
.search-result-item {
    margin-bottom: 40px;
    border-bottom: 1px solid #eee;
    padding-bottom: 30px;
}

/* ★ 共通の検索ボタンデザイン */
.mysearch-btn {
    background-color: #0073aa;
    color: #fff;
    border: none;
    padding: 8px 16px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    display: inline-block;
    white-space: nowrap;
}
.mysearch-btn:hover {
    background-color: #005f8d;
}

/* ★ 検索窓(共通) */
.mysearch-input {
    width: 200px;
    max-width: 100%;
    padding: 6px 10px;
    border: 1px solid #ccc;
    border-radius: 4px;
}


/* ★ 検索結果ページの見出し(H2/H3/H4)の高さを強制調整 */
.search-result-item h2,
.search-result-item h3,
.search-result-item h4,
.search-result-item .matched-block h2,
.search-result-item .matched-block h3,
.search-result-item .matched-block h4 {
    margin-top: 6px !important;
    margin-bottom: 6px !important;
    line-height: 1.5 !important;
        padding-top: 6px !important;
    padding-bottom: 6px !important;
}

</style>

<?php get_footer(); ?>

ここまで出来たらカテゴリー別検索を設置したいところにショートコードを設置すれば完了です。

今回は固定ページにNextcloud に関する検索ページを作成し、そこに設置してみました。
リンクはこちら

さいごに

検索を行う際にドロップダウンメニューでカテゴリー選択をできるようにとも考えたのですが、今回はNextcloud 関連に特化した検索ページを作成したかったので、初めからカテゴリー指定をすることにしまた。

少しでも参考になればと思ます。

? 元の記事を見る

Macスクリーンショット アプリ”Shottr”が1年ぶりにVer 1.9にアップデート 

Macの便利なスクリーンショットアプリ”Shottr”からアップデートが出ました。
最後にアップデートされたのが2024年11月28日なので約1年ぶりのアップデートとなっています。
ドロー系の新機能が結構役に立ちそうです。
また、Amazon Simple Storage Service に画像をアップロードできるのも便利かも。

アップデートの主な内容は

Shottrのホームページになるリリースノートに掲載されているアップデート内容です。

新機能

  • S3アップロード(S3対応ストレージ Amazon Simple Storage Service (S3))
  • 拡大鏡ツール
  • テキスト、矢印、楕円、四角形のオブジェクト用の手描きスタイル オプション。
  • あらゆる種類の矢印をアーチ状に曲げることができるようになりました。
  • 設定可能なオブジェクト スナップ。
    • S3アップロード(S3対応ストレージ Amazon Simple Storage Service (S3))
    • 拡大鏡ツール
    • テキスト、矢印、楕円、四角形のオブジェクト用の手描きスタイル オプション。
    • あらゆる種類の矢印をアーチ状に曲げることができるようになりました。
    • 設定可能なオブジェクト スナップ。

    改良点

  • ウィンドウが十分に大きい場合、16個のツールすべてがツールバーに表示されるようになりました
  • カスタム スタイルの通知は、ドラッグして閉じることができるようになりました (上にドラッグ)。
  • メニューバーのアイコンには、ファイルをコピー、保存、またはアップロードするときに簡単な確認アニメーションが表示されます。
    • ウィンドウが十分に大きい場合、16個のツールすべてがツールバーに表示されるようになりました
    • カスタム スタイルの通知は、ドラッグして閉じることができるようになりました (上にドラッグ)。
    • メニューバーのアイコンには、ファイルをコピー、保存、またはアップロードするときに簡単な確認アニメーションが表示されます。

    バグ修正

  • クリップボードへのコピーは、混雑したコンピュータでは遅延が発生する場合がありましたが、もう問題はありません。
  • バージョン1.8では、大きなウィンドウを優先するオプションに問題がありました。現在は修正されており、「デフォルトのズームレベル:100%を優先」に名前が変更されています。
  • macOS のスクロール キャプチャ問題の影響を受けるコンピューターでは、アプリは回避策を展開する必要があります。
  • Shottr は、macOS がホットキーでのアクティブ化の要求をブロックした場合に回避策を使用するようになりました。
  • 選択範囲が画像の下端に触れたときに自動調整機能で稀に発生するクラッシュを修正しました。
    • クリップボードへのコピーは、混雑したコンピュータでは遅延が発生する場合がありましたが、もう問題はありません。
    • バージョン1.8では、大きなウィンドウを優先するオプションに問題がありました。現在は修正されており、「デフォルトのズームレベル:100%を優先」に名前が変更されています。
    • macOS のスクロール キャプチャ問題の影響を受けるコンピューターでは、アプリは回避策を展開する必要があります。
    • Shottr は、macOS がホットキーでのアクティブ化の要求をブロックした場合に回避策を使用するようになりました。
    • 選択範囲が画像の下端に触れたときに自動調整機能で稀に発生するクラッシュを修正しました。

    新機能

    • S3アップロード(S3対応ストレージ Amazon Simple Storage Service (S3))
    • 拡大鏡ツール
    • テキスト、矢印、楕円、四角形のオブジェクト用の手描きスタイル オプション。
    • あらゆる種類の矢印をアーチ状に曲げることができるようになりました。
    • 設定可能なオブジェクト スナップ。

    改良点

    • ウィンドウが十分に大きい場合、16個のツールすべてがツールバーに表示されるようになりました
    • カスタム スタイルの通知は、ドラッグして閉じることができるようになりました (上にドラッグ)。
    • メニューバーのアイコンには、ファイルをコピー、保存、またはアップロードするときに簡単な確認アニメーションが表示されます。

    バグ修正

    • クリップボードへのコピーは、混雑したコンピュータでは遅延が発生する場合がありましたが、もう問題はありません。
    • バージョン1.8では、大きなウィンドウを優先するオプションに問題がありました。現在は修正されており、「デフォルトのズームレベル:100%を優先」に名前が変更されています。
    • macOS のスクロール キャプチャ問題の影響を受けるコンピューターでは、アプリは回避策を展開する必要があります。
    • Shottr は、macOS がホットキーでのアクティブ化の要求をブロックした場合に回避策を使用するようになりました。
    • 選択範囲が画像の下端に触れたときに自動調整機能で稀に発生するクラッシュを修正しました。

    Shottr リリースノートより

    新しい機能の説明はShottrのホームページに掲載されているビデオで分かりやすく説明されているのでぜひ確認ください。
    下の画像をクリックするとホームページが別タブで開くの画像の▶をクリックしてご覧ください。

    Shottr1.9

    今回のアップデートは結構いいかも!

    ? 元の記事を見る

    エックスサーバーでNextcloud Update 時の注意点

    NextcludをVersion 31.0.8にやっとアップデートしました。
    毎回、アップデートを行うたびに同じようなエラーや警告が出ています。そのたびに過去の投稿記事を確認しながらアップデートを行っていたのですが、複数の記事を見直す場合もあるのでポイントだけを簡単にリストして忘備録をのこすことにしました。

    アップデート中のエラー

    アップデート中にエラーとなったり止まったりする場合の対処は主に3つです。
    具体的な内容は下記記事を参考ください。
    この記事ではポイントだけを掲載してます。

    ダウンロード中や更新中に動かなくなる

    アップデート中にバックアップ作成時やダウンロード時に止まてしまった場合、Nextcloudをインストールしているディレクトリ内のdata フォルダにあるupdater-ocXXXXXXX フォルダーを削除して再度、初めからアップデートをやり直す。

    ModPagespeed Offのエラーをあらかじめ対処

    Nextcloudをインストールしているディレクトリ内のhtaccessのファイルを編集、ModPagespeed Offの前に#を付ける

    アップデート後の500エラー

    ###の行を改行

    # php — END cPanel-generated handler, do not edit#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####

    となっているものを

    AddDefaultCharset utf-8
    Options -Indexes
    #### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####

    改行して下記のようにする

    アップデート後の警告

    ヘッダーメモリーに関する警告

    インスタンスの一部のヘッダーが正しく設定されていません - `Strict-Transport-Security` HTTPヘッダーが設定されていません(少なくとも `15552000` 秒に設定する必要があります)。セキュリティを強化するために、HSTSを有効にすることを推奨します。 詳細については、ドキュメント↗を参照してください。

    Nextcloudをインストールしているディレクトリ内のhtaccessのファイルの最後に下記を追記

    Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains"

    参考記事はここ⇩

    インデックスがない

    アップデート後の警告でたまに下記のようにインデックスがないので追加しなさいという警告が出ます。

    Warning1
    いくつかの欠落しているオプションのインデックスを検出しました。データベースのパフォーマンスを向上させるために、(Nextcloudまたはインストールされたアプリケーションによって)新しいインデックスが追加されることがあります。インデックスの追加には時間がかかり、一時的にパフォーマンスが低下することがあるため、アップグレード時には自動的には行われません。インデックスが追加されると、それらのテーブルへのクエリが速くなるはずです。インデックスを追加するには、`occ db:add-missing-indices` コマンドを使用してください。 インデックスが不足: "unique_category_per_user" テーブル内の "vcategory" 詳細については、ドキュメント↗を参照してください。

    エックスサーバーの管理画面のphpMyAdminを使って不足しているインデックスを追記します。

    phpmyadmin1

    phpMyAdminのログイン用のIDとパスワードを覚えていない場合は、Nextcloudをインストールしているディレクトリ内のConfigフォルダのConfig.phpの中で確認することが出来ます。

    phpMyAdminにログインをして、左のファイルのリストから目的のテーブルを探します。

    phpmyadmin2

    今回は "vcategory" テーブルです。
    その中に目的のインデックス、今回は "unique_category_per_user"がないことが確認できたら、インデックスにある新規作成を選択し、インデックス名のところに unique_category_per_user と入力し実行をクリック。これでインデックスの作成は終了です。

    エックスサーバーでは対処できない警告

    Nexcloudの管理者設定画面に表示される警告等で、エックスサーバーのレンタルサーバーでは現時点で対処できないために下記警告は残ってしまいます。

    セットアップに関するいくつかのエラーがあります

  • セットアップに関するいくつかのエラーがあります。
  • いくつかのファイルは整合性チェックに合格していません。 無効なファイルのリスト… 再スキャン… 詳細については、ドキュメント↗を参照してください。
  • MariaDBバージョン "10.5.22-MariaDB-log" が検出されました。このバージョンのNextcloudで最高のパフォーマンス、安定性、機能性を実現するには、MariaDB >=10.6 および <=11.4を推奨します。
  • データベースがトランザクションファイルロックに使われています。パフォーマンスをあげるには、可能であればメモリーのキャッシュを設定してください。 詳細については、ドキュメント↗を参照してください。
  • このインスタンスには、いくつかの推奨 PHP モジュールがありません。パフォーマンスと互換性を向上させるために、これらをインストールすることを強くお勧めします: - sodium パスワードハッシュ用の Argon2 詳細については、ドキュメント↗を参照してください。
    • セットアップに関するいくつかのエラーがあります。
    • いくつかのファイルは整合性チェックに合格していません。 無効なファイルのリスト… 再スキャン… 詳細については、ドキュメント↗を参照してください。
    • MariaDBバージョン "10.5.22-MariaDB-log" が検出されました。このバージョンのNextcloudで最高のパフォーマンス、安定性、機能性を実現するには、MariaDB >=10.6 および <=11.4を推奨します。
    • データベースがトランザクションファイルロックに使われています。パフォーマンスをあげるには、可能であればメモリーのキャッシュを設定してください。 詳細については、ドキュメント↗を参照してください。
    • このインスタンスには、いくつかの推奨 PHP モジュールがありません。パフォーマンスと互換性を向上させるために、これらをインストールすることを強くお勧めします: - sodium パスワードハッシュ用の Argon2 詳細については、ドキュメント↗を参照してください。

    さいごに

    今回の忘備録ではあまり詳しい内容は記載していませんが、Nextcloudをアップデートすると必ず出てくるエラーや警告に対処するためのポイントだけをリストしました。
    詳しい内容や今回記載していないものについては過去の投稿記事を参考ください。

    ? 元の記事を見る

    カスタム投稿を活用して自分専用のメモ機能を実装してみた Update!

    プライベートメモ(Private Notes Manager) は、WordPressのカスタム投稿を使い「自分だけが見られるメモ」を追加できるプラグインです。

    これまでブログを運営する中で、やりたいことやアイデアなどを投稿の下書きとして書き留めてきました。
    しかし、実際の記事とは関係のないカスタマイズのメモや「いつかやってみたいこと」なども下書きに保存していたため、下書きばかりが増えてしまった…という経験があります。

    それを解決するために、投稿とは切り離してメモを管理でき、記事の整理にも役立つよう作成してみました。
    管理画面に表示されるメモは自分専用の備忘録として活用でき、他のユーザーや訪問者には一切表示されません。
    「公開するほどではないけれど、記録しておきたいこと」などアイディア次第でいろいろな用途に使用できるかもしれません。

    興味のある方は、プラグインとして配布していますので、ぜひご自由にお試しください。

    [wpdm_package id='1672']

    ・通知設定で”通知対象がないときもメール送信を行う”を有効にしていてもメールが送信されない場合がある不具合を修正。バージョンは Ver1.0.3となっています。

    ・投稿がない場合、ダッシュボードの期限メモ通知にエラーが表示される場合がある不具合を修正しました。 バージョンは Ver1.0.2となっています。

    ・メールでのカテゴリー別通知でメールが同じものが2通送信される不具合を修正しました。
    バージョンは Ver1.0.1となっています。

    ・通知設定で”通知対象がないときもメール送信を行う”を有効にしていてもメールが送信されない場合がある不具合を修正。バージョンは Ver1.0.3となっています。

    ・投稿がない場合、ダッシュボードの期限メモ通知にエラーが表示される場合がある不具合を修正しました。 バージョンは Ver1.0.2となっています。

    ・メールでのカテゴリー別通知でメールが同じものが2通送信される不具合を修正しました。
    バージョンは Ver1.0.1となっています。

    【プライベートメモ】とは

    【プライベートメモ】は、WordPress 管理画面上で個人用のメモを管理し、期限切れや期限が近いメモを自動的に通知できるものです。既存の投稿やカスタム投稿タイプとは独立してシンプルに使えるため、日々のちょっとした備忘録からタスク管理まで活用できると思ます。

    主な特徴

  • 投稿タイプ「プライベートメモ」にカテゴリ・タグを設定
  • 投稿記事に期限日を設定可能(カレンダー入力対応)
  • 完了/未完了のステータス管理
  • 通知メールを自動送信(期限切れ、当日、明日、数日前など柔軟設定)
  • 通知はメール、ダッシュボード、管理画面に表示
  • Gutenberg・クラシックエディター両対応
  • 今すぐ通知送信ボタンあり(テスト送信にも便利)
  • 通知メールの件名・本文・フッターをテンプレート化、自由にカスタマイズ
  • 専用のプリビュー表示と印字ボタン設置
    • 投稿タイプ「プライベートメモ」にカテゴリ・タグを設定
    • 投稿記事に期限日を設定可能(カレンダー入力対応)
    • 完了/未完了のステータス管理
    • 通知メールを自動送信(期限切れ、当日、明日、数日前など柔軟設定)
    • 通知はメール、ダッシュボード、管理画面に表示
    • Gutenberg・クラシックエディター両対応
    • 今すぐ通知送信ボタンあり(テスト送信にも便利)
    • 通知メールの件名・本文・フッターをテンプレート化、自由にカスタマイズ
    • 専用のプリビュー表示と印字ボタン設置

    初期設定

    設定といっても特には何もありませんが、1つだけ!
    管理画面左メニューの「設定」→「パーマリンク」を開き、何も変更しなくていいので【変更を保存】を押してください。
    カスタム投稿タイプを作成した後、WordPressに新しいURL構造を認識させ、カスタム投稿タイプの記事にアクセスできるようにするためです。
    また、通知は必要ないという場合は、通知設定画面で通知オプションをオフにしてください。

    通知設定画面について

    管理画面左メニューの「プライベートメモ」→「通知設定」を開くと通知設定画面は大きく分けて3つの部分となっています。
    ・通知表示オプション
    ・メールでの通知時間設定
    ・メールでの通知テンプレート

    順番に簡単な説明を行っていきます。

    通知表示オプション

    通知については、マイリスト以外は未完了の投稿だけが対象となります。

    Setup1

    各項目について

    通知を有効にする:通知機能を有効・無効の切り替え。必要のない場合にはチェックを外す(初期値はオン)
    通知方法:メール/管理画面/ダッシュボードから希望のものを選択、複数選択も可能(各サンプルを参照
    通知対象がないときもメール送信を行う: メール通知を対象がないときにも送信(初期値は有効)
    通知メール送信先:初期値はワードプレスで設定しているメールアドレスになります。(送信箇所は1か所のみ)
    通知対象:期限切れ、今日、明日、○日前、から希望の物を選択、複数選択も可能
    期限なし未完了メモも通知する:期限の設定されていない未完了投稿をすべて対象とする
    マイリストの通知対象化:有効にした場合、未完了・完了に関係なくすべてが対象となりる
    「○日前」の通知日数:期限日が何日先の物を対象とするか

    メールでの通知時間設定

    setup2

    通知は毎日自動で送信されます(WordPressのCronを使用)。
    各項目について

    通知送信時間:通知時刻はワードプレスで設定した時間帯が基準となります
    今すぐメール通知を送信 ですぐにメール送信可能
    サーバーCronを使用する:サーバーのCronを使用して自動送信を行う場合
    サーバーのCron コマンド:サーバー側でのcron設定時のコマンドとなります
    エディター選択: Gutenberg、 旧エディターの選択が可能

    メールでの内容確認等を行う際には手動で今すぐメール通知を送信ボタンを使ってテスト送信を行うことが出来ができます。
    ※ 通知方法でメールをオンにする必要があります

    通知送信時間について

    WordPressでは、疑似Cronが利用されており「誰かがサイトにアクセスしたとき」に定期処理が実行され、定した通知送信時間に送信します。そのため、サイトへのアクセスがないい場合には設定時間より遅延または送信されない場合もあります。
    より正確に確実にスケジュール送信を望む場合は、サーバー側でCronを設定することをお勧めします。
    設定の際には、サーバー側のCron実行時間を 通知送信時間と同じ又は通知時刻の後の時間に設定をする必要があります。

    メールでの通知テンプレート

    setup3

    各項目について

    メール件名テンプレート:件名はディフォルトで設定されていますが、ここで変更可能です。
    メール本文テンプレート:本文はディフォルトで設定されていますがプレスホルダーとを使い自由に設定可能
    プレースホルダーを挿入:6つの内容を投入可能です。(内容は下記参照)
    メモ一覧の表示形式:カテゴリー別一覧表示、期限別一覧表示の選択が可能(各サンプル参照
    メールフッター文:件名はディフォルトで設定されていますが、ここで変更可能です。
    テンプレートを初期化:メール件名、本文、メールフッターをディフォルトに戻します。

    メール本文で利用可能なプレースホルダー

    メール本文で利用可能なプレースホルダー

  • {datetime}:メールの通知日時
  • {today}:通知日の日付
  • {note_list}:対象メモ一覧リスト(カテゴリー一覧形式または期限別一覧形式)
  • {note_count}:対象メモの全体の件数
  • {site_name}:サイト名
  • {note_list_url}:投稿一覧へのリンク
    • {datetime}:メールの通知日時
    • {today}:通知日の日付
    • {note_list}:対象メモ一覧リスト(カテゴリー一覧形式または期限別一覧形式)
    • {note_count}:対象メモの全体の件数
    • {site_name}:サイト名
    • {note_list_url}:投稿一覧へのリンク

    管理画面通知のサンプル

    管理画面での表示項目は、通知設定で選択した対象になる通知の投稿数のみの表示となります。

    notifications1

    管理画面通知は、右上のⓍで再度ログインするまで非表示にできます。
    非表示にした後、手動で通知を更新しても次のログインまでは非表示となります。

    ダッシュボード ウィジェット通知のサンプル

    ダッシュボードでの通知については、通知設定で通知対象にした投稿のタイトル一覧が表示されます

    notifications2

    マイリストの対象メモが通知対象期限の中に含まれる場合には黄枠のように⚠️が表示されます。
    タイトル横の右端に表示されるリンクアイコンで直接編集画面へ移動ができます。
    通知は毎回、ワードプレスにログイン時に更新されますが、”通知を更新”で手動でも更新できます。
    手動で更新後、管理画面通知の下にメッセージが表示されます(管理画面通知のサンプル参照)

    メール通知のサンプル

    通知設定で通知方法でメールをオンにしている場合に、2通りの表示形式を選択が可能となります。
    またメール通知はhtml形式(リッチテキスト軽視)となります。

    ・カテゴリー別表示形式
    notifications_mail2

    赤矢印の[確認]をクリックするとメモの編集画面へリンクします。
    ※ リンクを開くとワードプレスへのログインが求められます。ログインしない場合には開くことはできません。
    マイリストをオンにしているメモについては、対象カテゴリーには含まれないようにしています。
    カテゴリーよりもマイリストを優先させ重複しないようにしています。

    ・期限別表示形式
    notifications_mail1

    赤矢印の[確認]をクリックするとメモの編集画面へリンクします。
    ※ リンクを開くとワードプレスへのログインが求められます。ログインしない場合には開くことはできません。
    期間別については、ダッシュボード通知と同じく、マイリストと重複するものについては黄枠のように表示されます。

    テスト・確認方法

    メールでの通知が機能しているか確認するには:

  • 「通知設定」画面で「今すぐ送信」ボタンをクリック
  • 登録済みのメモの中で、対象条件に合うものがあれば通知が届きます
  • メールが届かない場合は、迷惑メール フォルダーなどを確認、また送信先メールアドレスも確認ください
    1. 「通知設定」画面で「今すぐ送信」ボタンをクリック
    2. 登録済みのメモの中で、対象条件に合うものがあれば通知が届きます
    3. メールが届かない場合は、迷惑メール フォルダーなどを確認、また送信先メールアドレスも確認ください

    プライベートメモの使い方

    使い方といっても通常の投稿記事と同じで特にはありませんが念のため。
    プラグインを有効にすると左端のメニューに”プライベートメモ”が表示されます
    メニューは
    ・プライベートメモ ⇨ プライベートメモ投稿一覧
    ・投稿を追加 ⇨ 新規投稿を追加
    ・メモカテゴリ ⇨ メモ専用のカテゴリー
    ・メモタグ ⇨ メモ専用のタグ
    ・通知設定 ⇨ 通知関連の設定画面
    となります

    メモの追加

    管理画面左メニューの「プライベートメモ」→「投稿を追加」
    投稿編集画面はシンプルな構成となります
    右に表示されるサイドバーも項目を少なくしています

    memo_post

    ・右サイドバーで「期限日」を選択、なければ設定の必要なし
    ・「ステータス」で「未完了」 or 「完了」を選択、初期値は「未完了」
    ・「マイリスト」マイリストに追加するかしないか。マイリストに追加すると通知設定や一覧での絞り込みに対応
    ※ 保存をすると自動的に投稿のステータスが”公開”となります。またステータスの初期設定は”未完了”となります。

    プレビュー表示

    通常の投稿編集と同じようにプレビュー表示ができますが、プライベートメモ専用のプレビュー表示となります。

    preview

    本文以外(サイドバー、ヘッダー、フッターなど)は全て表示されないようになっています。
    本文の上に管理バーだけの表示となります。またスタイルについて、投稿編集画面では各テーマのスタイルが反映さていますが、プレビューではテーマのスタイルは反映されずシンプルなものになっています。
    タイトルの下にステータス(未完了・完了)と期限日があれば表示されます
    また、プレビュー画面の右上に”印刷する”ボタンを設置しています。

    プライベートメモ投稿一覧

    memo_list
  • 一覧画面上部からカテゴリ・タグ・ステータス・マイリストで絞り込みが可能
    ※ テーマによっては絞り込みボタンを押すと一覧に何も表示されなくなる場合がありますが、もう一度押すと絞り込みされた一覧が表示されます。
  • 期限日順でソート可能
  • 一覧上で各投稿の右端にあるステイタス切り替えボタンで「未完了・完了」の変更が可能
    • 一覧画面上部からカテゴリ・タグ・ステータス・マイリストで絞り込みが可能
      ※ テーマによっては絞り込みボタンを押すと一覧に何も表示されなくなる場合がありますが、もう一度押すと絞り込みされた一覧が表示されます。
    • 期限日順でソート可能
    • 一覧上で各投稿の右端にあるステイタス切り替えボタンで「未完了・完了」の変更が可能

    クイック編集

    quick_edit
  • クイック編集を開くと、ステータスがラジオボタンで変更が可能
  • カテゴリ、タグの選択が可能
  • 期限の設定・変更が可能
  • マイリストへの追加が可能
    • クイック編集を開くと、ステータスがラジオボタンで変更が可能
    • カテゴリ、タグの選択が可能
    • 期限の設定・変更が可能
    • マイリストへの追加が可能

    注意点

  • インストール後、必ずワードプレス設定にあるパーマリンクで保存をしてください。(何も変更する必要はありません、保存のみ行ってください)
  • プレビューで表示したくないもの、目次やスポンサーなどのassets/css/preview.cssに追記することで非表示にできます。
    1. インストール後、必ずワードプレス設定にあるパーマリンクで保存をしてください。(何も変更する必要はありません、保存のみ行ってください)
    2. プレビューで表示したくないもの、目次やスポンサーなどのassets/css/preview.cssに追記することで非表示にできます。

    参考までに下記がpreview.cssの内容となります。

    /* --- 通常画面用スタイル --- */
    body {
      font-family: sans-serif;
      background: none !important;
    }
    
    .print-button-container {
      text-align: right;
      margin: 1em 0;
    }
    
    .print-button {
      background: #0073aa;
      color: white;
      border: none;
      padding: 0.5em 1em;
      font-size: 1em;
      cursor: pointer;
      border-radius: 4px;
    }
    
    /* ステータスバッジ */
    .note-status-label {
      display: inline-block;
      padding: 0.3em 0.6em;
      border-radius: 4px;
      font-weight: bold;
      color: #000;
    }
    .note-status-complete {
      background-color: #c8f7c5;
    }
    .note-status-incomplete {
      background-color: #ffe9b3;
    }
    
    /* ステータスと期限日横並び */
    .note-meta {
      display: flex;
      flex-wrap: wrap;
      gap: 1.5em;
      margin-bottom: 1em;
      font-size: 1em;
      align-items: center; 
    }
    
    /* 見出し共通スタイル */
    .entry-header h1 {
      font-size: 2em;
      font-weight: 700;
      border-bottom: 3px solid #333;
      padding-bottom: 0.3em;
      margin-bottom: 1em;
      color: #222;
    }
    
    .entry-content h2,
    .entry-content h3,
    .entry-content h4,
    .entry-content h5,
    .entry-content h6 {
      margin-top: 2em;
      margin-bottom: 1em;
      font-weight: bold;
      color: #2a2a2a;
      position: relative;
      padding-left: 0.6em;
    }
    
    .entry-content h2::before,
    .entry-content h3::before,
    .entry-content h4::before,
    .entry-content h5::before,
    .entry-content h6::before {
      content: "";
      position: absolute;
      top: 0.1em;
      left: 0;
      width: 4px;
      height: 100%;
      background-color: #0073aa;
    }
    
    /* サイズ階層 */
    .entry-content h2 { font-size: 1.5em; }
    .entry-content h3 { font-size: 1.3em; }
    .entry-content h4 { font-size: 1.15em; }
    .entry-content h5 { font-size: 1em; }
    .entry-content h6 { font-size: 0.95em; }
    
    /* --- 印刷専用スタイル --- */
    @media print {
      .print-button-container,
      #wpadminbar {
        display: none !important;
      }
    
      * {
        -webkit-print-color-adjust: exact !important;
        print-color-adjust: exact !important;
      }
    
      .note-status-label {
        background-color: inherit !important;
        -webkit-print-color-adjust: exact !important;
        print-color-adjust: exact !important;
      }
    
      .note-status-complete {
        background-color: #c8f7c5 !important;
      }
    
      .note-status-incomplete {
        background-color: #ffe9b3 !important;
      }
    
      .entry-content h2::before,
      .entry-content h3::before,
      .entry-content h4::before,
      .entry-content h5::before,
      .entry-content h6::before {
        background-color: #0073aa !important;
      }
    }

    目次やスポンサーなどを非表示にしたい場合上記のpreview.cssに追記ください。
    下記は一例です。

    例:目次、スポンサーなどを非表示 ( Cocoonの場合)
    .toc-content {
    display: none;
    }
    .ad-label {
    display: none;
    }

    Q&A

    Q1. 通知されないメモがある

  • 通知設定で通知対象にチェックが入っているか確認
  • 期限日・ステータスが正しく設定されているか確認
    ※マイリスト以外は未完了の投稿のみが対象
    • 通知設定で通知対象にチェックが入っているか確認
    • 期限日・ステータスが正しく設定されているか確認
      ※マイリスト以外は未完了の投稿のみが対象

    Q2. Cron 実行時間を変更したい

  • 通知設定の「通知送信時間」を変更すれば、自動実行される時刻が更新されます
  • サーバー Cron を利用している場合は、サーバー側のスケジュール時刻も合わせて調整してください
    • 通知設定の「通知送信時間」を変更すれば、自動実行される時刻が更新されます
    • サーバー Cron を利用している場合は、サーバー側のスケジュール時刻も合わせて調整してください

    Q8. メール通知が届かないのですが?

    A. 通知メールが届かない場合、以下を確認してください:

  • 通知機能が「有効」になっているか(設定画面で確認)
  • 通知先メールアドレスが正しいか
  • サーバー側でメール送信が許可されているか(WP Mail SMTPなどで確認)
  • 通知対象が存在しない(未完了・期限内メモがない)場合は送信されない設定になっていないか
    • 通知機能が「有効」になっているか(設定画面で確認)
    • 通知先メールアドレスが正しいか
    • サーバー側でメール送信が許可されているか(WP Mail SMTPなどで確認)
    • 通知対象が存在しない(未完了・期限内メモがない)場合は送信されない設定になっていないか

    さいごに

    【プライベートメモ】は、できるだけシンプルにメモを残すことを目的としています。
    「ちょっとしたメモを非公開で残せたら…」という思いから自分だけのメモスペースとして、また日々の気づきややってみたことなどのメモとしてお役立立てれば嬉しいです。
    「こんなことも記録しておけばよかった」と思えるような気づきが増えていくかもしれません。
    気になった方は、ぜひ一度お試しください。

    ? 元の記事を見る

    ブログ記事タイトルや任意のテキスト・リンクをスクロール表示するプラグインを作ってみた

    ブログ記事タイトルや任意のテキスト、リンクをスクロール表示できる”スクロールマーキーウィジェット”を作ってみました。今まではHTMLでコードを書いて設置をしていたのですが、もっと簡単に設置したいと思いChatGPTに相談をしながら簡単にウィジェットで設定できるようにプラグイン化し作成してみました。ウィジェットで設定したものをショートコードでも設置可能としています。
    お知らせや注目情報、プロモーションリンクの掲出ができるようになっています。興味のある方は使ってみてください。(もちろん無料配布です)

    主な特徴

  • 投稿記事、任意のテキストや外部リンクが混在可能
  • 表示順序のカスタマイズ(例:投稿→テキスト→リンク)
  • 通常スクロール
  • 左端で一時停止付きスクロール
  • 中央で1件ずつ表示
  • パックマン風アニメーション
    • 通常スクロール
    • 左端で一時停止付きスクロール
    • 中央で1件ずつ表示
    • パックマン風アニメーション
  • スクロールスタイルを4種類から選択可能
    • 通常スクロール
    • 左端で一時停止付きスクロール
    • 中央で1件ずつ表示
    • パックマン風アニメーション
  • アイコン・文字色・背景色・フォントサイズ・背景の高さなどの見た目を設定可能
  • 表示速度やループ回数も調整可能
  • ウィジェットで設定した内容をショートコードで任意の場所に設置可能
    • 投稿記事、任意のテキストや外部リンクが混在可能
    • 表示順序のカスタマイズ(例:投稿→テキスト→リンク)
    • スクロールスタイルを4種類から選択可能
      • 通常スクロール
      • 左端で一時停止付きスクロール
      • 中央で1件ずつ表示
      • パックマン風アニメーション
    • アイコン・文字色・背景色・フォントサイズ・背景の高さなどの見た目を設定可能
    • 表示速度やループ回数も調整可能
    • ウィジェットで設定した内容をショートコードで任意の場所に設置可能

    [wpdm_package id='1521']

    ウィジェットの使い方

    管理画面でウィジェットを追加

    scrolling_items1
    scrolling_items1

    外観 > ウィジェット から「スクロールマーキーウィジェット」を任意のウィジェットエリアに追加します。

    外観 > ウィジェット から「スクロールマーキーウィジェット」を任意のウィジェットエリアに追加します。

    scrolling_items1

    外観 > ウィジェット から「スクロールマーキーウィジェット」を任意のウィジェットエリアに追加します。

    必要な情報を入力

    scrolling_items1a
    scrolling_items1a

    ① 表示順序 ‐ 投稿/テキスト/リンクの並び順を選択可能
    ② 表示スタイル ‐ 4つのスタイルから選択可能
    ③ 投稿ID ‐ 表示したい投稿のIDをカンマ区切りで入力(例:12,24,35)
    ④ 投稿リンクの開き方 ‐ 同一タブ・新しいタブ
    ⑤ テキスト ‐ 任意のテキストを1行ずつ入力
    ⑥ 外部リンク ‐ title=タイトル, url=https://example.comで入力
    ⑦ 外部リンクの開き方 ‐ 同一タブ・新しいタブ
    ⑧ アイコン/色 ‐ Font Awesomeクラス名で指定(例:fa fa-star)
    ⑨ フォントサイズ/色 - 任意の単位で入力(例:16px, 1.5em)
    ⑩ 行間/色 ‐ 背景の高さを任意の単位で入力(例:16px, 1.5em)
    ⑪ スクロール速度 ‐ 0.1 ~ 20 の範囲で調整(遅い~早い)
    ⑫ ループ回数 ‐ 0で無限ループ、指定回数で停止、非表示となる
    ⑬ このウィジェットのID ‐ ショートコードの内容表示

    ② 表示スタイル説明
    スクロール横スクロールで連続表示(標準)
    スクロール/一時停止1つずつスクロールし、左端で一時停止
    一件ずつ中央表示1件ずつ中央に表示しながらフェード
    パックマンパックマンが文字を食べるアニメーション(遊び心あり)

    ① 表示順序 ‐ 投稿/テキスト/リンクの並び順を選択可能
    ② 表示スタイル ‐ 4つのスタイルから選択可能
    ③ 投稿ID ‐ 表示したい投稿のIDをカンマ区切りで入力(例:12,24,35)
    ④ 投稿リンクの開き方 ‐ 同一タブ・新しいタブ
    ⑤ テキスト ‐ 任意のテキストを1行ずつ入力
    ⑥ 外部リンク ‐ title=タイトル, url=https://example.comで入力
    ⑦ 外部リンクの開き方 ‐ 同一タブ・新しいタブ
    ⑧ アイコン/色 ‐ Font Awesomeクラス名で指定(例:fa fa-star)
    ⑨ フォントサイズ/色 - 任意の単位で入力(例:16px, 1.5em)
    ⑩ 行間/色 ‐ 背景の高さを任意の単位で入力(例:16px, 1.5em)
    ⑪ スクロール速度 ‐ 0.1 ~ 20 の範囲で調整(遅い~早い)
    ⑫ ループ回数 ‐ 0で無限ループ、指定回数で停止、非表示となる
    ⑬ このウィジェットのID ‐ ショートコードの内容表示

    ② 表示スタイル説明
    スクロール横スクロールで連続表示(標準)
    スクロール/一時停止1つずつスクロールし、左端で一時停止
    一件ずつ中央表示1件ずつ中央に表示しながらフェード
    パックマンパックマンが文字を食べるアニメーション(遊び心あり)
    scrolling_items1a

    ① 表示順序 ‐ 投稿/テキスト/リンクの並び順を選択可能
    ② 表示スタイル ‐ 4つのスタイルから選択可能
    ③ 投稿ID ‐ 表示したい投稿のIDをカンマ区切りで入力(例:12,24,35)
    ④ 投稿リンクの開き方 ‐ 同一タブ・新しいタブ
    ⑤ テキスト ‐ 任意のテキストを1行ずつ入力
    ⑥ 外部リンク ‐ title=タイトル, url=https://example.comで入力
    ⑦ 外部リンクの開き方 ‐ 同一タブ・新しいタブ
    ⑧ アイコン/色 ‐ Font Awesomeクラス名で指定(例:fa fa-star)
    ⑨ フォントサイズ/色 - 任意の単位で入力(例:16px, 1.5em)
    ⑩ 行間/色 ‐ 背景の高さを任意の単位で入力(例:16px, 1.5em)
    ⑪ スクロール速度 ‐ 0.1 ~ 20 の範囲で調整(遅い~早い)
    ⑫ ループ回数 ‐ 0で無限ループ、指定回数で停止、非表示となる
    ⑬ このウィジェットのID ‐ ショートコードの内容表示

    ② 表示スタイル説明
    スクロール横スクロールで連続表示(標準)
    スクロール/一時停止1つずつスクロールし、左端で一時停止
    一件ずつ中央表示1件ずつ中央に表示しながらフェード
    パックマンパックマンが文字を食べるアニメーション(遊び心あり)

    表示スタイルについて

    スタイル名説明
    スクロール横スクロールで連続表示(標準)
    スクロール/一時停止1つずつスクロールし、左端で一時停止
    一件ずつ中央表示1件ずつ中央に表示しながらフェード
    パックマンパックマンが文字を食べるアニメーション(遊び心あり)

    補足注意事項

    項目注意事項
    投稿ID投稿IDはカンマ区切りで入力することにより複数の投稿記事タイトルを順番に表示することが可能です。表示される順番は入力順となります。
    テキスト
    外部リンク
    改行で複数表示可能
    表示される順番は入力順となります。
    このウィジェットのIDウィジェットの設定を保存した時点で正式なショートコードIDが表示されます
    ”ショートコードで使用するには”に表示されているショートコードをお使いください
    アイコン
    フォントサイズ
    行間
    アイコンのサイズはフォントサイズと同じになります。
    フォントサイズについて初期設定(空白時)は17px
    行間の初期設置(空白時)は35px
    ※ スタイルでパックマンを選択した場合、パックマンのの大きさはフォントサイズの1.4倍の

    ショートコードでの使用方法

    ショートコードを使えば、記事本文や固定ページ内にもウィジェットで設定した内容で表示が可能です。

    使用例

    scrolling_items3
    scrolling_items3

    設定したウィジェットを保存後、入力画面の下の部分に表示される ”ショートコードで使用するには” で表示されたショートコードを好きな場所に貼り付けて設置可能

    例:
    [scrolling_items widget_id="scroll_marquee_widget-18"]

    ショートコードだけで設置したい場合は、ウィジェットを設定後、ウィジェットを非表示することで設置可能
    ショートコードを使って複数設置する場合、競合してうまく表示されないことがあります。
    特に、投稿記事ページで複数表示の場合(例:本文中、サイドバー、タイトル上などに同時に設置した場合)

    設定したウィジェットを保存後、入力画面の下の部分に表示される ”ショートコードで使用するには” で表示されたショートコードを好きな場所に貼り付けて設置可能

    例:
    [scrolling_items widget_id="scroll_marquee_widget-18"]

    ショートコードだけで設置したい場合は、ウィジェットを設定後、ウィジェットを非表示することで設置可能
    ショートコードを使って複数設置する場合、競合してうまく表示されないことがあります。
    特に、投稿記事ページで複数表示の場合(例:本文中、サイドバー、タイトル上などに同時に設置した場合)

    scrolling_items3

    設定したウィジェットを保存後、入力画面の下の部分に表示される ”ショートコードで使用するには” で表示されたショートコードを好きな場所に貼り付けて設置可能

    例:
    [scrolling_items widget_id="scroll_marquee_widget-18"]

    ショートコードだけで設置したい場合は、ウィジェットを設定後、ウィジェットを非表示することで設置可能
    ショートコードを使って複数設置する場合、競合してうまく表示されないことがあります。
    特に、投稿記事ページで複数表示の場合(例:本文中、サイドバー、タイトル上などに同時に設置した場合)

    ウィジェットを非表示について

    テーマでCocoonを使っている場合には、各ウィジェットの入力画面に”表示設定”がありますので、そこで”チェック・入力したページで表示する”を選択し全てのチェックがされていない状態で保存をします。
    また、Cocoon以外のテーマを使用し、ウィジェットの入力画面に”表示設定”がない場合には、こちらのプラグインで代用することが可能ですのでご利用ください。
    ※ 表示モードで非表示期間設定にするだけです。

    使用にあたっての注意事項

    このプラグインは同じページ内に1つのスクロールマーキーウィジェットを設置する前提で作りこんでいます。
    その為、複数のスクロールマーキーウィジェットを別スタイルで設置した場合、お互いの表示スタイルが干渉し正しく表示されない場合があります。

    ? 元の記事を見る

    WordPressのウィジェットに表示期間の設定を追加するプラグインを作ってみた

    以前投函した【表示期間等を指定できるウィジェット”Scheduled Widge”を作って実装してみた】で紹介したウィジェットですが、使い勝手を良くしたいと思いワードプレスで使用している全てのウィジェットに追加機能として組み込むように変更をし、プラグイン化してみました。
    ウィジェットスケジュール指定追加機能は、WordPressのウィジェット表示に「表示期間・曜日・時間帯・除外期間」などの条件設定を加えることができる便利なプラグインです。
    イベント告知・キャンペーン・期間限定の案内など、「ある時間だけウィジェットを表示したい」というものに活用できます。
    興味のある方はお試しください。(もちろん無料配布です

    主な機能

  • 表示開始日/終了日の設定
    指定した期間のみウィジェットを表示できます。
  • 曜日の指定
    曜日ごとに表示・非表示を制御できます。
  • 時間帯の設定
    午前・午後などの時間単位で制御できます。
  • 除外期間の設定
    通常の表示期間中でも、一部の期間だけ除外できます。
  • ウィジェット個別に設定
    すべてのウィジェットに共通して、個別設定が可能です。
  • 不要な設定はスキップ可能
    条件を何も設定しなければ、通常通り表示されます。
    • 表示開始日/終了日の設定
      指定した期間のみウィジェットを表示できます。
    • 曜日の指定
      曜日ごとに表示・非表示を制御できます。
    • 時間帯の設定
      午前・午後などの時間単位で制御できます。
    • 除外期間の設定
      通常の表示期間中でも、一部の期間だけ除外できます。
    • ウィジェット個別に設定
      すべてのウィジェットに共通して、個別設定が可能です。
    • 不要な設定はスキップ可能
      条件を何も設定しなければ、通常通り表示されます。

    ダウンロードは⇩から

    [wpdm_package id='1522']

    確認済み動作環境・ワードプレス Ver 6.8
    ・Cocoon Ver 2.8.6
    ・php Ver2.8.5.3php Ver 8.2.22

    使用例

  • 期間限定セールバナーを指定の日時だけ表示
  • 週末だけ特別なメッセージを表示
  • 深夜帯には特定の案内を非表示にする
  • イベント前の告知と、イベント終了後の非表示
    • 期間限定セールバナーを指定の日時だけ表示
    • 週末だけ特別なメッセージを表示
    • 深夜帯には特定の案内を非表示にする
    • イベント前の告知と、イベント終了後の非表示

    使い方

    説明するほどの内容ではありませんが念のため。

    schedule1
    schedule1

    プラグインをインストール後、有効化するとウィジェットの下の方に赤矢印のように”表示期間設定を開く/閉じる” ボタンが表示されます。
    ボタンを押すと入力フォームが現れます。


    ※ 青い矢印の部分はChuyaさん公開の ”ウィジェットに「PC・モバイル表示切り替え」オプションをついかする方法” を利用させていただいています。

    プラグインをインストール後、有効化するとウィジェットの下の方に赤矢印のように”表示期間設定を開く/閉じる” ボタンが表示されます。
    ボタンを押すと入力フォームが現れます。


    ※ 青い矢印の部分はChuyaさん公開の ”ウィジェットに「PC・モバイル表示切り替え」オプションをついかする方法” を利用させていただいています。

    schedule1

    プラグインをインストール後、有効化するとウィジェットの下の方に赤矢印のように”表示期間設定を開く/閉じる” ボタンが表示されます。
    ボタンを押すと入力フォームが現れます。


    ※ 青い矢印の部分はChuyaさん公開の ”ウィジェットに「PC・モバイル表示切り替え」オプションをついかする方法” を利用させていただいています。

    表示期間設定ボタンを押すと入力フォームが表示されます。

    各項目の内容は

    ① 現在のサーバー時間 ‐ この時間が設定時の基本日時となります

    ② 表示モード ‐ 表示期間/非表示期間の選択

    ③ 開始日時/終了日時設定

    ④ 曜日指定 ‐ 対象となる曜日を指定できます

    ⑤ 除外期間設定 ‐ 設定期間中に除外期間を設定したい場合に使用。
    ※  ”除外期間を設定する” にチェックを入れたときだけ入力画面が表示されます。

    各項目の内容は

    ① 現在のサーバー時間 ‐ この時間が設定時の基本日時となります

    ② 表示モード ‐ 表示期間/非表示期間の選択

    ③ 開始日時/終了日時設定

    ④ 曜日指定 ‐ 対象となる曜日を指定できます

    ⑤ 除外期間設定 ‐ 設定期間中に除外期間を設定したい場合に使用。
    ※  ”除外期間を設定する” にチェックを入れたときだけ入力画面が表示されます。

    各項目の内容は

    ① 現在のサーバー時間 ‐ この時間が設定時の基本日時となります

    ② 表示モード ‐ 表示期間/非表示期間の選択

    ③ 開始日時/終了日時設定

    ④ 曜日指定 ‐ 対象となる曜日を指定できます

    ⑤ 除外期間設定 ‐ 設定期間中に除外期間を設定したい場合に使用。
    ※  ”除外期間を設定する” にチェックを入れたときだけ入力画面が表示されます。

    期間等の入力後、保存をすると後で設定をしたかどうかが分かるように表示を変えています。

    schedule3
    schedule3

    表示期間などの設定項目がある場合には赤枠のように”設定あり”が表示されます。

    設定あり”は下記の場合に表示されます。
    ・開始日/終了日が入力されている
    ・時間が入力されている
    ※ 開始日の時間の欄が、ブランクまたは00:00以外、終了日の時間の欄がブランクまたは23:59以外の時間が入力されている。
    ・曜日が1つでもチェックが外れている場合
    ・”除外期間を設定する”にチェエクが入っている場合
    ※ 除外期間の設定に日時の入力の有無にかかわらずチェエクされている場合

    表示期間などの設定項目がある場合には赤枠のように”設定あり”が表示されます。

    設定あり”は下記の場合に表示されます。
    ・開始日/終了日が入力されている
    ・時間が入力されている
    ※ 開始日の時間の欄が、ブランクまたは00:00以外、終了日の時間の欄がブランクまたは23:59以外の時間が入力されている。
    ・曜日が1つでもチェックが外れている場合
    ・”除外期間を設定する”にチェエクが入っている場合
    ※ 除外期間の設定に日時の入力の有無にかかわらずチェエクされている場合

    schedule3

    表示期間などの設定項目がある場合には赤枠のように”設定あり”が表示されます。

    設定あり”は下記の場合に表示されます。
    ・開始日/終了日が入力されている
    ・時間が入力されている
    ※ 開始日の時間の欄が、ブランクまたは00:00以外、終了日の時間の欄がブランクまたは23:59以外の時間が入力されている。
    ・曜日が1つでもチェックが外れている場合
    ・”除外期間を設定する”にチェエクが入っている場合
    ※ 除外期間の設定に日時の入力の有無にかかわらずチェエクされている場合

    注意事項

    キャッシュ関連のプラグインを使用している場合は、キャッシュが更新されないと期間指定がうまく作動しません。
    キャッシュ更新については各プラグインの注意事項等をご確認ください。
    WP Fastest Cache プラグインを使用している場合は下記記事をご参考ください。

    ? 元の記事を見る

    投稿画面から直接タイトル入りアイキャッチ画像を作れるツールを作ってみた!

    ちょっとした投稿(ニュース投稿など)では、アイキャッチ画像を毎回用意するのが面倒で、いつも同じ画像を使い回していました。そんな中、Cocoonにはタイトルから自動でアイキャッチ画像を生成してくれる便利な機能が実装されており、画像がないときにはとても助かっています。
    ただ、背景画像を選べない点が少し物足りなく感じていました。
    そこで今回は、投稿画面からそのままタイトル入りのアイキャッチ画像を作成できるツールを、ChatGPTの力を借りて実装してみました。
    もし興味があれば、ぜひ参考にしてみてください!

    プラグイン化してみました。

    表示するテキストの入力欄で改行を行った場合にアイキャッチ画像へされなかったのを反映するようにjsファイルを修正しました。こちらに掲載されているjsコードは修正済みです。プラグインもアップデートしました。

    更新情報

    表示するテキストの入力欄で改行を行った場合にアイキャッチ画像へされなかったのを反映するようにjsファイルを修正しました。こちらに掲載されているjsコードは修正済みです。プラグインもアップデートしました。

    長い日本語タイトルで、自動的に改行されず画面からはみ出し表示されない問題を自動的に改行して表示されるように修正しました。こちらに掲載されているjsコードは修正済みです。プラグインもアップデートしました。(Ver1.2)

    7/21更新情報

    長い日本語タイトルで、自動的に改行されず画面からはみ出し表示されない問題を自動的に改行して表示されるように修正しました。こちらに掲載されているjsコードは修正済みです。プラグインもアップデートしました。(Ver1.2)

    ツールの紹介

    このツールは、WordPressの投稿編集画面に「タイトル入りアイキャッチ画像作成」パネルを追加し、アイキャッチ画像にタイトルをオーバレイさせた合成画像の作成、または画像がないときに背景色を選んでタイトルをオーバレイさせた画像を作成できるツールです。
    編集はリアルタイムでプリビューで表示されるので保存前に確認することが出来ます。また、作成後はアイキャッチに設定されるようになっています。

    主な機能一覧

    機能内容
    背景画像選択以下から選択可能
    ・ 既存のアイキャッチ画像を使用
    ・新しく画像をアップロード
    ・単色背景
    (単色背景画像は 1200x630px 固定)
    テキスト設定投稿タイトルが自動で入力され、以下のカスタマイズが可能
    ・タイトルではなく別のテキストに変更可能
    ・文字サイズ(10 〜 200px)
    ・文字色
    ・フォントスタイル(普通/太字/斜体/太字+斜体)
    ・影(有無、色、ぼかし)
    ・フォント(テーマのNotoSansJPを利用)
    配置調整タイトルの位置を以下から選択可能
    ・横位置:%指定
    ・縦位置:%指定
    背景オーバーレイ文字の背景に半透明の長方形を追加可能
    ・背景色
    ・透過度
    リアルタイムプレビューCanvasで描画し、調整内容がすぐに画面に反映されるプレビュー付き
    保存処理「保存」ボタンで画像を生成し、以下を自動で実行
    ・PNG形式でWordPressメディアライブラリに保存
    ・生成画像をその投稿のアイキャッチに設定
    ※ 保存後、投稿編集ページを更新するとことでアイキャッチ設定プリビューに表示されます
    対応投稿タイプ全ての投稿タイプに対応
    確認済み動作環境・ワードプレス Ver 6.8
    ・Cocoon Ver 2.8.5.3
    ・php Ver2.8.5.3php Ver 8.2.22

    インストールの方法

    インストールについて今回は、function.phpとjavascript.jsに追記するのではなく、新たにファイルを作成し設置する方法で行っています。
    function.phpのファイルが大きくなること、また整理しやすくするためにこのツール用のファイルを作成し、そこに設置することにしています。

    手順としては
    1.function.phpに作成したphpファイルを読み込むコードを追記
    2.追加function用とjavascript.用のフォルダ作成を作成
    3.ファイルのアップロード
    となります。

    function.phpの読みこみ等については下記Chuyaさんの記事を参考にしていますのでご確認ください。

    1.function.phpに作成したphpファイルを読み込むコードを追記

    Cocoonの子テーマにあるfunction.phpに下記コードを追記

    /********************************
    functionファイルの分割・読み込み
    ********************************/
    
    // 子ディレクトリ取得
    $dir = get_stylesheet_directory();
    
    // functions下のファイル取得
    $files = glob($dir . '/functions/*.php');
    
    foreach($files as $file) {
      if (file_exists($file)) {
        include($file);
      }
    }

    追加function用とjavascript.用のフォルダを作成

    Cocoonのcocoon-child-masterのフォルダに新規にfunctionとjsのフォルダを作成。

    Eye_catch1

    これで下準備は完了です。

    ファイルのアップロード

    下記コードを張り付けたphp、jsファイルを作成し、functionとjsフォルダにアップロードすれば完了です。

    <?php  // eye_catch_with_title.php
    
    function add_custom_featured_image_metabox() {
         $post_types = get_post_types(['public' => true], 'names');
        foreach ($post_types as $post_type) {
            add_meta_box(
                'custom_featured_image',
                'カスタムアイキャッチ画像作成',
                'render_custom_featured_image_metabox',
                $post_type,
                'side'
            );
        }
    }
    add_action('add_meta_boxes', 'add_custom_featured_image_metabox');
    
    function render_custom_featured_image_metabox($post) {
        $thumbnail_id = get_post_thumbnail_id($post->ID);
        $thumbnail_url = $thumbnail_id ? wp_get_attachment_url($thumbnail_id) : '';
        ?>
    
     <style>
            #custom-featured-image-tool {
                display: flex;
                flex-wrap: wrap;
                gap: 10px; /* パネル間のスペース */
            }
    
            .panel {
                flex: 1 1 calc(50% - 20px); /* 2列レイアウト */
                background: #f9f9f9; /* パネルの背景色 */
                border: 1px solid #ddd; /* 枠線 */
                padding: 15px; /* 内側の余白 */
                border-radius: 8px; /* 角を丸く */
                box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* 影 */
            }
    
            .panel p {
                margin: 0 0 10px; /* パラグラフの下の余白 */
            }
    
            button {
                width: 100%; /* ボタンをフル幅に */
            }
        </style>
    
        <div id="custom-featured-image-tool">
            <canvas id="preview-canvas" style="max-width:100%; border:1px solid #ccc;"></canvas>
    
            <!-- 背景モード選択 -->
           <div class="panel">
                <p><strong>背景の選択</strong></p>
                <label><input type="radio" name="bg-mode" value="upload" checked> 画像をアップロード</label><br>
                <label><input type="radio" name="bg-mode" value="color"> 背景色のみ</label>色指定:<input type="color" id="bg-color" value="#000000"><br>
    
                <div id="upload-wrapper" style="margin-top: 8px;">
                    <div id="upload-wrapper" style="margin-top: 8px;">
        <label for="custom-bg-upload" class="upload-label">画像をアップロード</label>
        <input type="file" id="custom-bg-upload" accept="image/*" style="display: none;">
        <div id="file-name-display">ファイルが選択されていません</div>
    </div>
    
    <style>
      .upload-label {
        display: inline-block;
        padding: 0.5em 1em;
        background-color: #007cba;
        color: #fff;
        border-radius: 4px;
        cursor: pointer;
        margin-bottom: 0.1em;
      }
    
      .upload-label:hover {
        background-color: #006ba1;
      }
    
      #file-name-display {
        font-size: 12px;
        color: #333;
        margin-top: 0.1em;
      }
    </style>
    
    <script>
      document.addEventListener('DOMContentLoaded', function () {
        const fileInput = document.getElementById('custom-bg-upload');
        const fileDisplay = document.getElementById('file-name-display');
    
        fileInput.addEventListener('change', function () {
          if (fileInput.files.length > 0) {
            fileDisplay.textContent = fileInput.files[0].name;
          } else {
            fileDisplay.textContent = 'ファイルが選択されていません';
          }
        });
      });
    </script>
    <br>
                    <img id="upload-preview" src="" style="max-width: 100%; margin-top: 8px; display:none;">
                </div>
    
                <label><input type="checkbox" id="overlay-enable" checked> タイトル背景色</label><br>
                オーバーレイの色: <input type="color" id="overlay-color" value="#000000"><br>
                透明度:<input type="range" id="overlay-opacity" min="0" max="1" step="0.05" value="0.4">
                </div>
          
            <!-- テキスト位置 -->
            <div class="panel">
    <p><strong>テキスト位置</strong></p>
                横:<input type="number" id="pos-x" value="50" min="0" max="100" style="width: 30%;">%,
                縦:<input type="number" id="pos-y" value="50" min="0" max="100" style="width: 30%;">%
            </div>
    <!-- 任意テキスト -->
    <div class="panel">
        <p><strong>表示するテキスト</strong></p>
        <label><input type="checkbox" id="custom-text-enable"> タイトルの代わりに以下のテキストを使う</label><br>
        <textarea id="custom-text" style="width: 100%; height: 60px;" placeholder="ここに任意のテキストを入力..."></textarea>
    </div>
    
            <!-- スタイル -->
        <div class="panel">
            <p><strong>スタイル</strong></p>
            フォントサイズ: <input type="number" id="font-size" value="24" min="10" max="200" style="width: 30%;"><br>
            <div style="display: flex; align-items: center; gap: 10px;">
      <label for="font-style">スタイル:</label>
      <select id="font-style" style="width: 40%;">
        <option value="normal">普通</option>
        <option value="bold">太字</option>
        <option value="italic">斜体</option>
        <option value="bold italic">太字+斜体</option>
      </select>
    </div>
            </select>
                フォント種類:<br>
                <select id="font-family" style="width: 100%;">
                    <option value="sans-serif">ゴシック体(sans-serif)</option>
                    <option value="serif">明朝体(serif)</option>
                    <option value="monospace">等幅(monospace)</option>
                    <option value="cursive">手書き風(cursive)</option>
                </select>
        
            <!-- テキスト色 -->
                 文字色: <input type="color" id="text-color" value="#ffffff">シャドウ: <input type="checkbox" id="text-shadow" checked>
           <!-- パディング -->
                 背景余白<br>
                上下:<input type="number" id="padding-vertical" value="10" min="0" style="width: 30%;">
                左右:<input type="number" id="padding-horizontal" value="10" min="0" style="width: 30%;">
            </div>
    
            <div class="panel">
      <button type="button" id="generate-image" class="button button-primary" style="width: 100%;">画像を生成して保存</button>
      <div id="save-status" style="margin-top: 8px; font-weight: bold; color: #0073aa;"></div>
    </div>
    
        <script>
            const postTitle = <?php echo json_encode(get_the_title($post)); ?>;
            const postID = <?php echo (int) $post->ID; ?>;
            const thumbnailURL = <?php echo json_encode($thumbnail_url); ?>;
        </script>
        <?php
        wp_enqueue_script('custom-featured-image-tool', get_stylesheet_directory_uri() . '/js/custom-featured-image-tool.js', [], null, true);
        wp_localize_script('custom-featured-image-tool', 'customImageAjax', [
            'ajax_url' => admin_url('admin-ajax.php'),
            'nonce'    => wp_create_nonce('custom_featured_image')
        ]);
    }
    
    add_action('wp_ajax_save_custom_featured_image', 'save_custom_featured_image');
    function save_custom_featured_image() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'custom_featured_image')) {
            wp_send_json_error('認証に失敗しました');
        }
    
        $post_id = intval($_POST['post_id']);
    
        if (!isset($_FILES['image_file']) || $_FILES['image_file']['error'] !== UPLOAD_ERR_OK) {
            wp_send_json_error('画像ファイルのアップロードに失敗しました');
        }
    
        $file = $_FILES['image_file'];
        $upload_dir = wp_upload_dir();
        $filename = 'custom_featured_' . $post_id . '_' . time() . '.png';
        $filepath = $upload_dir['path'] . '/' . $filename;
    
        if (!move_uploaded_file($file['tmp_name'], $filepath)) {
            wp_send_json_error('画像の保存に失敗しました');
        }
    
        $filetype = wp_check_filetype($filename, null);
        $attachment = [
            'post_mime_type' => $filetype['type'],
            'post_title'     => sanitize_file_name($filename),
            'post_content'   => '',
            'post_status'    => 'inherit'
        ];
    
        $attach_id = wp_insert_attachment($attachment, $filepath, $post_id);
        require_once ABSPATH . 'wp-admin/includes/image.php';
        $attach_data = wp_generate_attachment_metadata($attach_id, $filepath);
        wp_update_attachment_metadata($attach_id, $attach_data);
    
        set_post_thumbnail($post_id, $attach_id);
    
        wp_send_json_success('保存成功');
    }

    今回はファイル名をeye_catch_with_title.phpにしてあります。
    黄色ハイライトの部分は必要ないです。ファイル名を確認するために記述しているだけですので <?php だけ記述してあればいいです。

    下記 JSファイルはファイル名 custom-featured-image-tool.jsにて作成しています。
    jsファイル名、フォルダ名を変更する場合には上記 phpの赤色ハイライト部分のファイル名、フォルダ名を変更してください

    下のjsコードで黄色ハイライトの部分は必要ないです。ファイル名を確認するために記述しているだけです

    // public/js/custom-featured-image-tool.js
    
    document.addEventListener('DOMContentLoaded', () => {
      const canvas = document.getElementById('preview-canvas');
      const ctx = canvas.getContext('2d');
    
      let customBGImage = null;
      let bgMode = 'upload';
    
      const uploadInput = document.getElementById('custom-bg-upload');
      const uploadPreview = document.getElementById('upload-preview');
    
      uploadInput.addEventListener('change', (e) => {
      const file = e.target.files[0];
      if (file && file.type.startsWith('image/')) {
        const reader = new FileReader();
        reader.onload = function (evt) {
          const img = new Image();
          img.onload = function () {
            customBGImage = img;
            bgMode = 'upload'; // ← これを追加
            uploadPreview.src = evt.target.result;
            drawImage();
          };
          img.src = evt.target.result;
        };
        reader.readAsDataURL(file);
      }
    });
    
    document.getElementById('bg-color').addEventListener('input', () => {
      bgMode = 'color'; // ← 背景色変更時に切り替え
      drawImage();
    });
    
    
      document.querySelectorAll('input[name="bg-mode"]').forEach(radio => {
        radio.addEventListener('change', (e) => {
          bgMode = e.target.value;
          document.getElementById('upload-wrapper').style.display = (bgMode === 'upload') ? 'block' : 'none';
          drawImage();
        });
      });
    
    function drawCanvasContents(img = null) {
      const fontSize = parseInt(document.getElementById('font-size').value, 10);
      const fontStyle = document.getElementById('font-style').value;
      const fontFamily = document.getElementById('font-family').value;
      const textColor = document.getElementById('text-color').value;
      const textShadow = document.getElementById('text-shadow').checked;
      const paddingV = parseInt(document.getElementById('padding-vertical').value, 10);
      const paddingH = parseInt(document.getElementById('padding-horizontal').value, 10);
      const posXPercent = parseInt(document.getElementById('pos-x').value, 10) / 100;
      const posYPercent = parseInt(document.getElementById('pos-y').value, 10) / 100;
      const overlay = document.getElementById('overlay-enable').checked;
      const overlayColor = document.getElementById('overlay-color').value || '#000000';
      const overlayOpacity = parseFloat(document.getElementById('overlay-opacity').value || '0.4');
      const customTextEnabled = document.getElementById('custom-text-enable').checked;
      const customText = document.getElementById('custom-text').value.trim();
      const displayText = (customTextEnabled && customText) ? customText : postTitle;
    
      ctx.font = `${fontStyle} ${fontSize}px ${fontFamily}`;
      ctx.textBaseline = 'top';
      ctx.fillStyle = textColor;
    
    const maxTextWidth = canvas.width * 0.8;
    const lineHeight = fontSize * 1.4;
    
    const rawLines = displayText.split('\n'); // ← ユーザー入力の改行を反映
    const wrappedLines = [];
    
    rawLines.forEach((rawLine) => {
      let line = '';
      for (let char of rawLine) {
        const testLine = line + char;
        if (ctx.measureText(testLine).width > maxTextWidth) {
          wrappedLines.push(line);
          line = char;
        } else {
          line = testLine;
        }
      }
      if (line) wrappedLines.push(line);
    });
    const totalHeight = wrappedLines.length * lineHeight;
    const x = canvas.width * posXPercent;
    const y = canvas.height * posYPercent - totalHeight / 2;
    
      wrappedLines.forEach((line, i) => {
        const textWidth = ctx.measureText(line).width;
        const tx = x - textWidth / 2;
        const ty = y + i * lineHeight;
    
        if (overlay) {
          const radius = 12;
          const rectX = tx - paddingH;
          const rectY = ty - paddingV / 2;
          const rectWidth = textWidth + paddingH * 2;
          const rectHeight = fontSize + paddingV;
    
          ctx.save();
          ctx.fillStyle = hexToRGBA(overlayColor, overlayOpacity);
          roundRect(ctx, rectX, rectY, rectWidth, rectHeight, radius);
          ctx.fill();
          ctx.restore();
        }
    
        if (textShadow) {
          ctx.shadowColor = 'rgba(0,0,0,0.6)';
          ctx.shadowOffsetX = 2;
          ctx.shadowOffsetY = 2;
          ctx.shadowBlur = 4;
        } else {
          ctx.shadowColor = 'transparent';
        }
    
        ctx.fillStyle = textColor;
        ctx.fillText(line, tx, ty);
      });
    }
    
      function drawImage() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
    
      if (bgMode === 'upload' && customBGImage) {
        canvas.width = customBGImage.width;
        canvas.height = customBGImage.height;
        ctx.drawImage(customBGImage, 0, 0);
        drawCanvasContents();
      } else if (bgMode === 'color') {
        canvas.width = 1200;
        canvas.height = 630;
        const bgColor = document.getElementById('bg-color').value;
        ctx.fillStyle = bgColor;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        drawCanvasContents();
      } else if (thumbnailURL) {
        const img = new Image();
        img.crossOrigin = 'anonymous';
        img.onload = () => {
          canvas.width = img.width;
          canvas.height = img.height;
          ctx.drawImage(img, 0, 0);
          drawCanvasContents();
        };
        img.src = thumbnailURL;
      } else {
        // 何も設定されていないときのデフォルト背景色
        canvas.width = 1200;
        canvas.height = 630;
        ctx.fillStyle = "#cccccc";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        drawCanvasContents();
      }
    }
    
      document.querySelectorAll('#custom-featured-image-tool input, #custom-featured-image-tool select, #custom-text')
      .forEach(input => input.addEventListener('input', drawImage));
    
    
     document.getElementById('generate-image').addEventListener('click', () => {
      const saveButton = document.getElementById('generate-image');
      const statusEl = document.getElementById('save-status');
    
      // ボタンを無効化
      saveButton.disabled = true;
      saveButton.textContent = '保存中...';
      statusEl.textContent = '保存中...';
      statusEl.style.color = 'blue';
    
      canvas.toBlob(blob => {
        const formData = new FormData();
        formData.append('action', 'save_custom_featured_image');
        formData.append('post_id', postID);
        formData.append('nonce', customImageAjax.nonce);
        formData.append('image_file', blob, 'featured-image.png');
    
        fetch(customImageAjax.ajax_url, {
          method: 'POST',
          body: formData
        })
        .then(response => response.json())
        .then(data => {
          if (data.success) {
            statusEl.textContent = '保存とアイキャッチ画像の設定が出来ました!';
            statusEl.style.color = 'green';
          } else {
            statusEl.textContent = '保存失敗: ' + (data.data || '原因不明');
            statusEl.style.color = 'red';
          }
        })
        .catch(error => {
          statusEl.textContent = '通信エラー: ' + error.message;
          statusEl.style.color = 'red';
        })
        .finally(() => {
          // ボタンを再び有効化
          saveButton.disabled = false;
          saveButton.textContent = '画像を保存';
        });
      }, 'image/png');
    });
    
    
    
      function hexToRGBA(hex, alpha) {
        const r = parseInt(hex.slice(1, 3), 16);
        const g = parseInt(hex.slice(3, 5), 16);
        const b = parseInt(hex.slice(5, 7), 16);
        return `rgba(${r},${g},${b},${alpha})`;
      }
    
      function roundRect(ctx, x, y, width, height, radius) {
        ctx.beginPath();
        ctx.moveTo(x + radius, y);
        ctx.arcTo(x + width, y, x + width, y + height, radius);
        ctx.arcTo(x + width, y + height, x, y + height, radius);
        ctx.arcTo(x, y + height, x, y, radius);
        ctx.arcTo(x, y, x + width, y, radius);
        ctx.closePath();
      }
    
      drawImage();
    });

    ※ 文字化けが起きたら、ファイルを保存するときの文字コードを確認してみてください。(通常はUTF-8)

    使い方

    投稿画面の右の下の方に表示される

    Eye_catch1s

    プリビュー画面が小さくて見にくい場合は、青まるで囲んだ Λ をクリックして上に移動していくと投稿編集画面の下に大きく表示されます。

    Eye_catch2

    各メニューについて

    プリビュー画像の下にある各メニューについてです。
    メニューは大きく分けて5つのセクションからなり、それぞれの機能を説明していきたいと思います。

    Eye_catch_menu

    背景の選択

    項目補足説明
    画像をアップロード初期設定で選択済みアイキャッチ画像を表示
    背景色のみ色指定選択した背景色がプレビューに表示される
    背景画像は1200 X 630pxで固定
    選択した場合、”画像をアップロード”ボタンは非表示となる
    画像をアップロードアップロードした画像は即時プレビューに表示される
    ”画像を生成して保存”を行わないとアップロードされた画像はメディア ライブラリーに保存されない
    アップロードた場合、使用中のファアイル名がボタン下に表示される
    タイトル背景色タイトルに背景色の設定を有効・無効の選択
    オーバーレイの色タイトルの背景色の選択
    透明度タイトル背景色の透明度の調整

    テキスト位置

    項目補足説明
    テキスト位置テキストの位置調整
    縦・横ともに0 〜 100%で調整可能
    初期値は50%(中央)
    ※ 表示されたテキストの中心を基準にしているためプリビューを確認しながらの調整が必要

    表示するテキスト

    項目補足説明
    表示するテキストタイトルの代わりに別の内容のテキストを使用したい場合に選択、入力した内容がプリビューに表示される

    スタイル

    項目補足説明
    フォントサイズフォントサイズは10 〜 200pxで選択可能
    ※ 画像サイズによってプレビューでの見え方が変わります
    プリビューに表示される画像の縮小率に合わせてテキストの表示も縮小表示されているため
    スタイル4つの文字スタイルから選択可能
    ・普通
    ・太字
    ・斜体
    ・太字+斜体
    フォント種類4つのフォントから選択可能
    ・ゴシック体(sans-serif)
    ・明朝体(serif)
    ・等幅(monospace)
    ・手書き風(cursive)
    文字色文字の色の選択
    シャドウ文字にシャドウを有無の選択
    ※ プレビューで確認しながらオン/オフを選択した方が良い
    背景余白テキスト背景色の上下・左右の余白を設定
    ※ 背景の選択でタイトル背景色を選択していない場合は無効となります

    ⑤画像を生成して保存について

    アイキャッチ画像の編集後、”画像を生成して保存”を行うとメディア ライブラリに画像が新しく保存されます。
    アイキャッチの元の画像には上書きされず新しい画像として保存されます。
    保存が問題なく行われると”保存とアイキャッチ画像の設定が出来ました!”のメッセージが表示されます。
    また、保存がされた時点でアイキャッチ画像に設定がされるようになっていますが、右上のアイキャッチ画像のプリビュー表示は投稿画面のページを更新するまでは更新表示されまさせん。またページを更新する際には、記事内容が失われないように下書き保存(保存)を行ってからページ更新を行なって下さい。
    ※ 画像のサイズが大きい場合には、保存設定に時間がかかる場合があります。

    アイキャッチがうまく保存されない?

    画像が大きすぎる場合には、ModSecurityに関連したWAF セキュリティー設定によっては、うまく保存ができない場合があるようです。

    さいごに

    ワードプレステーマ Cocoonのでしか動作確認はしていません。
    他のテーマに実装したい場合は、テーマに合わせ一部のコードを変更すれば動くと思います。
    また、使用については自己責任となりますので、バックアップ等必要な準備をすることをお勧めします。
    もしできればコメント等もいただければ幸いです。

    追記、Cocoon以外でも使えるようにプラグイン化

    Cocoon以外でも使えるようにプラグイン化してみたのでお試しください。

    [wpdm_package id='1458']

    ? 元の記事を見る

    Nextcloud 警告 “One or more mimetype migrations are available”への対処

    Nextcloudをアップデートするたびに、「One or more mimetype migrations are available」という警告が表示されていました。これまでは RepairMimeTypes.php の一部を修正して対応していましたが、それが通用しなくなってしまいました。
    SSH を使った対処方法はいくつかあるようですが、今回はレンタルサーバーで SSH を使わずに解決する方法を見つけたので、試した内容を備忘録として残すことにしました。
    使用しているレンタルサーバーはエックスサーバーです。

    毎回表示される警告の一部

    アップデートを行うたびに表示される警告です

    occ-repair1

    One or more mimetype migrations are available. Occasionally new mimetypes are added to better handle certain file types. Migrating the mimetypes take a long time on larger instances so this is not done automatically during upgrades. Use the command `occ maintenance:repair --include-expensive` to perform the migrations.

    いままでは、ファイルの一部を修正して警告を消すようにしていました。

    ところがこれでは警告が消えない場合もあるようです。
    現在、2つのNextcloudを設置しています。2つのうち1つはバックアップ、テスト用として設置しているのですが、上のファイルの一部を修正して、1つのNextcloud は警告が消えるのに、もう一つの方は消えないという現象が起きています。環境は同じで、Nextcloudを初めにインストールしたときのバージョンが違うくらいです。

    とりあえず対処すべくいろいろと調べるとレンタルサーバーのSSHコマンドを使っての対処方法は見つかったのですが、SSHは知識がないためほとんど使わないことからSSHを使わずに対処する方法をChatGPTで調べたところ、対処することが出来ました。
    今回の対処したバージョンは、Nextcloud Hub 9 (30.0.8)です。

    対処方法

    対処方法は、SSHコマンドの代わりにエックスサーバーのCRON設定を使って対処する方法です。
    これならエックスサーバーの管理画面で対処することが出来ます。

    CRONでocc maintenanceを実行させる

    エックスサーバーのサーバー管理よりCRON設定追加を行います。
    今回は1回だけすぐに実行したいので、時間を5分後に実行するように設定します。

    Cron1

    CRONのコマンドは下記となります。

    /usr/bin/php8.1 /home/ユーザー名/nextcloud/occ maintenance:repair --include-expensive

    黄色のハイライトのphpの部分は使用中のバージョン、青色の部分はNextcloudがインストールされているアドレスに変更してください。(occ.phpファイルのあるアドレスになります。)

    Nextcloudをドメイン直下、public_htmlに直接インストールしている場合は、こんな感じで

    /usr/bin/php8.3 /home/ユーザー名/XXXXXX.com/public_html/occ maintenance:repair --include-expensive

    CRONで実行後、警告が消えたらCRON設定を削除して終了です。
    警告が消えない場合、Nextcloudのルートダイレクトリーにある ”occ.php” ファイルのパーミッションをエックスサーバーのファイル管理より "644" から ”744”に変更してCRONを再度実行、警告が消えたらパーミッションをもとの ”644”に戻す。
    警告が消えない場合は、次の対処方法へ進み、警告が消えるた時点でパーミッションを元に戻す。

    CRON実行でエラーが出る

    今回パーミッションを変更しCron実行後、メールで下記のようなエラーメッセージが届いてしまいました。
    Nextcloudの警告はまだ表示されています。

    An unhandled exception has been thrown:
    OCP\HintException: [0]: Memcache OC\Memcache\APCu not available for local cache (Is the matching PHP module installed and enabled?)

    内容は、Nextcloud が APCuをローカルキャッシュとして使用しようとしているが、サーバーでAPCuモジュールが有効になっていないというもののようです。
    さっそく追加で対処していきます。

    ①サーバーパネルからphp.iniの設定を変更してみる

    エックスサーバーのサーバーパネルの ”php.ini設定” を開き、APCuキャッシュを使えるように下記記述を追加します。

    apcu.enable_cli=1

    追加が出来たら、CRONを再度実行します。

    php.iniに追加設定をしても同じエラーメッセージが来てしまいました。
    ということで、いったんphp.iniに追加した apcu.enable_cli=1を削除し、次の対処へ。

    ②Nextcloudの config.php を修正してみる

    次に、Nextcloudで設定されているAPCu を無効にし、Filesystemキャッシュに変更してみます。

    エックスサーバーのファイルマネージャーで Nextcloudの”config.php” を開き記述の一部を変更します。
    ファイルの場所 は,Nextcloudの”config フォルダ”の中にある”config.php”です。

    変更するのは1か所、memcache.local の設定でAPCu使用になっているのをFilesystem Cache使用に変更する

    'memcache.local' => '\OC\Memcache\APCu',

    'memcache.local' => '\OC\Memcache\Filesystem',
    に変更する

    変更前 'memcache.local' => '\OC\Memcache\APCu', (APCuを使用)
    変更後 'memcache.local' => '\OC\Memcache\Filesystem', (Filesystem Cache を使用)

    変更を保存したら再度、CRONを実行させる。
    今度は、下記のようなメールが来ました。

    An unhandled exception has been thrown:
    OCP\HintException: [0]: Memcache OC\Memcache\Filesystem not available for local cache (Is the matching PHP module installed and enabled?)

    内容は、Nextcloudが Filesystem キャッシュを使用しようとしたが、使用できないとのこと。
    ということで、次の対処へ。

    ③ 'memcache.local'をコメントアウトしてみる

    'memcache.local' => '\OC\Memcache\APCu'でつまづいているようなので、この部分をコメントアウトし、設定を一時的に無効にしてみます。
    コメントアウトは”//”を期日の前に追加するだけです。

    // 'memcache.local' => '\OC\Memcache\APCu',

    コメントアウト出来たら、CRONを再度実行します。
    警告が消えたら、コメントアウトしたものを元に戻します。

    これで警告が消え、エラーメッセージも来ませんでした。
    やっと対処終了です。これで消えました。

    さいごに

    SSHコマンドを使わない対処方法でしたが、ファイルを編集する場合には必ずバックアップ等を取ることをお勧めします。
    エックスサーバーでの対処方法を掲載していますが、他のレンタルサーバーでも同じ内容にて対処可能だと思いますのでご参考になれば幸いです。
    また、警告がまだ消えないという場合、ChatGPTで検索してみると解決策が見つかるかもしれません。

    ? 元の記事を見る

    Nextcloud 31.0.0にアップデート後の警告対処

    Nexrcloud バージョン31が 2/25 にリリースされたようですが、アップデートしようかどうか悩んでいたのですがアップデートすることにしました。今回はテスト用に使っているNextcloudで先に試してから問題がなければ実用版もアップデートしようと思います。
    気になるのは、アップデートしたらOnlyoffieが問題なく使えるようになるかどうかだったのですが、残念ながら状況は変わらず、むしろ悪くなってしまいました。
    Onlyofficeは別として、アップデート後の警告への対処方法を忘備録として残すことにしましたので、これからアップデートを行う方の少しでも参考になれば幸いです。

    Nextcloud31にアップデート後、Onlyofficeが起動しなくなる場合があるようです。
    試しに、Onlyofficeのバージョン9.5を入れるとエラー画面になり、またバージョン9.7にしたところOnlyofficeの設定画面で保存を押すと警告が表示されOnlyofficeを起動することが出来なくなっています。
    全ての方が同じような状況になるかどうかは分かりませんが、Onlyofficeを使用するのであればアップデートはしばらく待った方がいいかもしれません。

    Onlyofficeを利用している方へ

    Nextcloud31にアップデート後、Onlyofficeが起動しなくなる場合があるようです。
    試しに、Onlyofficeのバージョン9.5を入れるとエラー画面になり、またバージョン9.7にしたところOnlyofficeの設定画面で保存を押すと警告が表示されOnlyofficeを起動することが出来なくなっています。
    全ての方が同じような状況になるかどうかは分かりませんが、Onlyofficeを使用するのであればアップデートはしばらく待った方がいいかもしれません。

    見慣れない警告が大量に

    アップデートする際には、いつも同じ警告内容が表示されるのですが、今回は赤枠のように見慣れない大量の警告が出ていました。
    赤枠以外の毎回表示される警告は過去の記事と同様に対処していきます。
    今回は赤枠の部分についてです。
    記載されている内容を見ると、各データベースのフォーマットをDynamicに変更しなさい。というもののようです。

    Nexcloud_Update31_Error

    さっそく赤枠のに記載されているデータベースのフォーマット変更をしていきたいと思います。

    phpMyAdminでデータベース フォーマットを変更

    今回もエックスサーバーのサーバーパネルよりphpMyAdminを起動して変更をしていきます。
    起動するには、ユーザー名とパスワードが必要です。
    忘れた方は、Nextcouldをインストールしたダイレクトリーにあるフォルダー”Config”にある”config.php”ファイルで確認することが出来ます。

    対象となるデータベースを左側のリストより選択、次に右画面の上にあるタブ一覧より”操作”を選択し、下の方にある”ROW_FORMAT”で”DYNAMIC"を選択し右下の”実行”を押せば完了です。

    Nexcloud_Update31_Error2

    これを一件一件行うことになります。
    ただ、よく見ると100件ぐらいあったので、一括で行う方法はないかと調べてらありました!

    SQLで一括変更

    左に表示されている一番上のデータベース名を選択し、右の画面 左上にある”SQL”タブを選択します。
    すると下に入力画面が表示されるので、変更を行いたい内容を赤枠のリストのように張り付けて、下にある”実行”をクリックすれば完了です。

    Nexcloud_Update31_Error1

    変更を行う内容のリストは、下記のような感じです。

    ALTER TABLE oc_accounts ROW_FORMAT=DYNAMIC;
    ALTER TABLE oc_accounts_data ROW_FORMAT=DYNAMIC;
    ALTER TABLE oc_activity ROW_FORMAT=DYNAMIC;
    ALTER TABLE oc_activity_mq ROW_FORMAT=DYNAMIC;
    ALTER TABLE oc_whats_new ROW_FORMAT=DYNAMIC;
    (同じように、警告に表示されたテーブルをすべて記載する)

    黄色い部分が警告で表示されたデータベース名となります。
    今回、件数が多かったのであらかじめエクセルでリストを作成し張り付けて実行することにしました。
    (リストをエクセルに取込み、”AFTER TABLE”と”ROW_FORMAT=DYNAMIC;”を前後に結合して作成)

    これで消せる警告は全て対処できました。
    相変わらず3つはどうしようもなく残ったままですが...。

    さいごに

    今回のアップデートで、Onlyofficeが使えなくなってしまいました。
    問題は、NextcloudにインストールしているComunity Document Serverの問題のようです。
    現在のバージョンが0.1.20で、最後に更新されたのが 2024年11月なのですが、Nextcloud31にはまだ対応していないようです。そのためOnlyofficeも動かなくなったようです。
    Nexcloud30まではOnlyoffice バージョン9.5で使うことが出来ていたのですがちょっと残念です。

    これからNexcloud 31にアップデートを検討している方は、ご注意ください。

    ? 元の記事を見る

    ONLYOFFICEを9.5から9.6にアップデートするとエラー、ダウングレードで対処する方法

    NextcloudのOnlyofficeを最新のVer9.6.0にアップデートするとエラーでOnlyofficeが起動しないという不具合が発生しているようです。
    2/15現在対処中のようですが、いろいろと調べた結果、すぐにでもエクセル等での編集を行いたい場合の対処としては、バージョンをダウングレードするしかないようです。
    調べてもなかなかダウングレード方法が見つからなかったのですが、やっとダウングレード方法を見つけましたので、忘備録として残しておくことにしました。
    Onlyofficeが使えなくて困っている方の参考になればと思います。

    Ver9.5へのダウングレード方法

    まずはバージョン9.5のファイルを下記アドレスからダウンロードします。

    tar.gzファイルの解凍についてはこちら

    Zip形式のファイルがgithubでダウンロード可能です。
    こちらから過去のバージョンをダウンロードできます

    追加情報

    Zip形式のファイルがgithubでダウンロード可能です。
    こちらから過去のバージョンをダウンロードできます

    次に、Nextcloudの右上のオプションから【アプリ】を選択し、左ウィンドウのメニューから【アクティブなアプリ】を選択。
    右のアプリのリストから”Onlyoffice”を選択し【無効にする】を選択し、Onlyofficeを無効化します。

    Onlyoffice_ver_error1

    次に、Nextcloudダイレクトリー /app/onlyoffice の全てのファイルを削除し、ダウンロードしたバージョン9.5のファイルを解凍し /app/onlyofficeへアップロードします。

    Onlyoffice_ver_error2

    アップロードが終了したら、Nextcloudの管理画面の【アプリ】の【無効なアプリ】からOnlyofficeを選択し【有効にする】を選択します。
    この時くれぐれも 9.6.0にアップグレードをしないように

    Onlyoffice_ver_error3

    最後に、念のためNextcloudの右上のオプションから【管理者設定】からOnlyoffiの設定画面に行き、【保存】を押して、エラーメッセージが出ないか確認をします。
    これでエラーが出なくなっていれば完了です。

    Onlyoffice_ver_error4

    onlyoffice.tar.gzファイルについて

    onlyoffice.tar.gzファイルは圧縮されっているので解凍しなければアップロードできません。
    解凍するにはツールが必要になりますが、ウェブ上でも解凍することが出来ます。
    今回は、下記サイトでファイルを解凍させていただきました。ちょっと時間はかかりましたが問題なく解凍できました。

    さいごに

    あくまでも今すぐOnlyofficeを使いたい場合の対処方法となります。
    出来れば、不具合の対策済みのバージョンアップを待てるのであれば、待つことをお勧めします。

    ? 元の記事を見る

    表示期間等を指定できるウィジェット”Scheduled Widge”を作って実装してみた

    期限付きの広告表示には、これまでCocoonのキャンペーンショートコードを使っていました。しかし、1点気になることがあり、最近は【Widget Visibility Time Scheduler】を利用していたのですが、最近になってもう少し細かく期限設定をしたいと思い、いろいろと調べてみたのですが良いものがなかなか見つかりませんでした。そこで、新たにウィジェットを追加して対応することにしました。
    この記事では、新しく実装したウィジェットの設定方法と使い方、そしてCocoonのキャンペーンショートコードで気になっていた点について、忘備録として残すことにしました。

    気がつかなかったのですが、サイドバー以外の本文等にこのウィジェットを設置した場合、レイアウト崩れが発生し、サイドバーが本文の下に表示されてしまう不具合があったようです。そのため一部の記述を修正しアップデートしました。また修正した内容はここで確認できます。

    修正しました

    気がつかなかったのですが、サイドバー以外の本文等にこのウィジェットを設置した場合、レイアウト崩れが発生し、サイドバーが本文の下に表示されてしまう不具合があったようです。そのため一部の記述を修正しアップデートしました。また修正した内容はここで確認できます。

    Cocoonのキャンペーンショートコードについて

    Cocoonのキャンペーンコードは、期間の始まりと終わりを指定して、教示したいコンテンツの内容を記述するがけで簡単に設定できとても使いやすいです。

    Saidebar2

    ただ、気になるのは期限が切れて表示する対象の物がないときに、一部のスキンで空白のブロックが表示されてしまうことです。

    Saidebar1

    この空白ブロックを消せないかといろいろと試したのですが、見つからず【Widget Visibility Time Scheduler】を使うことにしました。

    簡単なWidget Visibility Time Schedulerの使い方

    ここでちょっと簡単に【Widget Visibility Time Scheduler】の使い方を。
    プラグイン追加より【Widget Visibility Time Scheduler】を検索してインストール、有効化するだけで準備は完了です。
    基本設定とは特になく、すぐに使うことが出来ます。

    使い方は、各ウィジェットの下の方に新しく”Open scheduler”のオプションが表示されるます。

    Scheduler1

    ”Open scheduler”をクリックすると設定項目が表示されます。

    ”予約投稿”で”表示” 又は”非表示”を選択した時点で、期間指定ができるようになります。
    指定は、期間、時間、そして曜日の指定ができます。

    Scheduler2

    予約投稿で”ー選択ー”のまま表示/非表示を選択しない場合は、期間指定はされず、通常のウィジェットとして機能します。

    Scheduler3

    指定期間が終わり、または非表示期間の場合にはウィジェットのブロックも表示されなくなります。

    After_Saidebar

    これでCocoonのキャンペーンショートコードで気になった、空白ブロックの表示問題が解消されることになります。

    指定期間中の除外期間を設定したい

    【Widget Visibility Time Scheduler】を使っていて、設定期間中に除外期間の設定をしたいと思いいろいろと試していたのですがうまくいかず、新たに対応できるウィジェットを追加設定することにしました。
    ただ今回は、【Widget Visibility Time Scheduler】のように全てのウィジェットに追加機能とするのではなく単体の新しいウィジェットとして追加をすることにしました。

    Scheduled Widgeの特徴

    新しく追加するウィジェットの機能はとてもシンプルで期間を指定して表示/非表示ができるというものです。
    ・HTML,ショートコードに対応
    ・表示期間と非表示期間を選べる
    ・曜日で表示/非表示が設定できる
    ・設定期間中に除外期間を設定することが出来る
    が主な機能となります。

    ウィジェットの追加設定はFunction.phpに貼り付けるだけ

    下記内容をFunction.phpに貼り付けるだけで追加設定は終わりです。

    /***********************************************
    Scheduled_Widget 期間指定できるウィジェット
    ***********************************************/
    
    class Scheduled_Widget extends WP_Widget {
        public function __construct() {
            parent::__construct('Scheduled_widget', __('Scheduled Widget', 'text_domain'));
        }
    
        public function widget($args, $instance) {
            $current_time = current_time('Y-m-d H:i');
            $current_day = date('w');
    
            $title = apply_filters('widget_title', $instance['title'] ?? '');
            $content = $instance['content'] ?? '';
    
            $start_datetime = !empty($instance['start_date']) ? $instance['start_date'] . ' ' . $instance['start_time'] : '1970-01-01 00:00';
            $end_datetime = !empty($instance['end_date']) ? $instance['end_date'] . ' ' . $instance['end_time'] : '1970-01-01 00:00';
    
            // 除外期間
            $exclude_start_datetime = !empty($instance['exclude_start_date']) ? $instance['exclude_start_date'] . ' ' . $instance['exclude_start_time'] : '1970-01-01 00:00';
            $exclude_end_datetime = !empty($instance['exclude_end_date']) ? $instance['exclude_end_date'] . ' ' . $instance['exclude_end_time'] : '2099-01-01 00:00';
    
            $visibility = $instance['visibility'] ?? 'show';
            $selected_days = $instance['days'] ?? [0, 1, 2, 3, 4, 5, 6];
    
            $is_within_time_range = ($current_time >= $start_datetime && $current_time <= $end_datetime);
            $is_selected_day = in_array($current_day, $selected_days);
            $is_within_exclude_range = ($current_time >= $exclude_start_datetime && $current_time <= $exclude_end_datetime);
    
                if (($visibility == 'show' && $is_within_time_range && $is_selected_day && !$is_within_exclude_range) ||
                ($visibility == 'hide' && (!$is_within_time_range || !$is_selected_day || $is_within_exclude_range))) {
                echo $args['before_widget'];
                if (!empty($title)) {
                    echo $args['before_title'] . $title . $args['after_title'];
                }
                  // ショートコード出力時に <p> タグを付けない
        echo do_shortcode($content);
        echo $args['after_widget']; // ← ここで1回だけ出力
    }
        }
    
        public function form($instance) {
            $title = $instance['title'] ?? '';
            $content = $instance['content'] ?? '';
            $start_date = $instance['start_date'] ?? '';
            $end_date = $instance['end_date'] ?? '';
            $start_time = $instance['start_time'] ?? '00:00';
            $end_time = $instance['end_time'] ?? '23:59';
            $visibility = $instance['visibility'] ?? 'show';
            $selected_days = $instance['days'] ?? [0, 1, 2, 3, 4, 5, 6];
    
            $exclude_start_date = $instance['exclude_start_date'] ?? '';
            $exclude_end_date = $instance['exclude_end_date'] ?? '';
            $exclude_start_time = $instance['exclude_start_time'] ?? '00:00';
            $exclude_end_time = $instance['exclude_end_time'] ?? '23:59';
    
     $is_exclude_enabled = !empty($exclude_start_date) || !empty($exclude_end_date); // 除外期間が設定されているか
    
            $start_of_week = get_option('start_of_week', 1);
            $days_of_week = range(0, 6);
            $days_of_week = array_merge(array_slice($days_of_week, $start_of_week), array_slice($days_of_week, 0, $start_of_week));
    
            $weekdays = [
                0 => '日曜日',
                1 => '月曜日',
                2 => '火曜日',
                3 => '水曜日',
                4 => '木曜日',
                5 => '金曜日',
                6 => '土曜日',
            ];
    // Get current server time
        $current_time = current_time('Y-m-d H:i:s');
        
        ?>
       
            <!-- タイトルとコンテンツのフォーム -->
            <p>
                <label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('タイトル:', 'text_domain'); ?></label>
                <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>">
            </p>
    
            <p>
                <label for="<?php echo $this->get_field_id('content'); ?>"><?php _e('コンテンツ:', 'text_domain'); ?></label>
                <textarea class="widefat" id="<?php echo $this->get_field_id('content'); ?>" name="<?php echo $this->get_field_name('content'); ?>"><?php echo esc_textarea($content); ?></textarea>
            </p>
    
            <!-- 可視設定 -->
            <p>
                <label for="<?php echo $this->get_field_id('visibility'); ?>"><?php _e('表示設定:', 'text_domain'); ?></label>
                <select class="widefat" id="<?php echo $this->get_field_id('visibility'); ?>" name="<?php echo $this->get_field_name('visibility'); ?>">
                    <option value="show" <?php selected($visibility, 'show'); ?>><?php _e('表示', 'text_domain'); ?></option>
                    <option value="hide" <?php selected($visibility, 'hide'); ?>><?php _e('非表示', 'text_domain'); ?></option>
                </select>
            </p>
    
            <!-- 開始日と終了日 -->
     <p><strong>現在のサーバー時間: <?php echo esc_html($current_time); ?></strong></p>       
     <p>
                <label for="<?php echo $this->get_field_id('start_date'); ?>"><?php _e('開始日:', 'text_domain'); ?></label>
                <input class="widefat" id="<?php echo $this->get_field_id('start_date'); ?>" name="<?php echo $this->get_field_name('start_date'); ?>" type="date" value="<?php echo esc_attr($start_date); ?>">
            </p>
    
            <p>
                <label for="<?php echo $this->get_field_id('start_time'); ?>"><?php _e('開始時間:', 'text_domain'); ?></label>
                <input class="widefat" id="<?php echo $this->get_field_id('start_time'); ?>" name="<?php echo $this->get_field_name('start_time'); ?>" type="time" value="<?php echo esc_attr($start_time); ?>">
            </p>
    
            <p>
                <label for="<?php echo $this->get_field_id('end_date'); ?>"><?php _e('終了日:', 'text_domain'); ?></label>
                <input class="widefat" id="<?php echo $this->get_field_id('end_date'); ?>" name="<?php echo $this->get_field_name('end_date'); ?>" type="date" value="<?php echo esc_attr($end_date); ?>">
            </p>
    
            <p>
                <label for="<?php echo $this->get_field_id('end_time'); ?>"><?php _e('終了時間:', 'text_domain'); ?></label>
                <input class="widefat" id="<?php echo $this->get_field_id('end_time'); ?>" name="<?php echo $this->get_field_name('end_time'); ?>" type="time" value="<?php echo esc_attr($end_time); ?>">
            </p>
    
            <!-- 曜日選択 -->
            <p>
                <label><?php _e('曜日設定:', 'text_domain'); ?></label><br>
                <?php foreach ($days_of_week as $day) : ?>
                    <label>
                        <input type="checkbox" name="<?php echo $this->get_field_name('days'); ?>[]" value="<?php echo $day; ?>" <?php checked(in_array($day, $selected_days)); ?>>
                        <?php echo $weekdays[$day]; ?>
                    </label><br>
                <?php endforeach; ?>
            </p>
    
      <!-- 除外期間の表示切り替え用チェックボックス -->
            <p>
                <input type="checkbox" id="<?php echo $this->get_field_id('enable_exclude'); ?>" <?php checked($is_exclude_enabled); ?>>
                <label for="<?php echo $this->get_field_id('enable_exclude'); ?>"><?php _e('除外期間設定', 'text_domain'); ?></label>
            </p>
    
            <!-- 除外期間フォーム(初期状態はチェックありの時だけ表示) -->
            <div id="<?php echo $this->get_field_id('exclude_settings'); ?>" style="display: <?php echo $is_exclude_enabled ? 'block' : 'none'; ?>;">
                <h3><?php _e('除外期間:', 'text_domain'); ?></h3>
    
                <p>
                    <label for="<?php echo $this->get_field_id('exclude_start_date'); ?>"><?php _e('開始日:', 'text_domain'); ?></label>
                    <input class="widefat" id="<?php echo $this->get_field_id('exclude_start_date'); ?>" name="<?php echo $this->get_field_name('exclude_start_date'); ?>" type="date" value="<?php echo esc_attr($exclude_start_date); ?>">
                </p>
    
                <p>
                    <label for="<?php echo $this->get_field_id('exclude_start_time'); ?>"><?php _e('開始時間:', 'text_domain'); ?></label>
                    <input class="widefat" id="<?php echo $this->get_field_id('exclude_start_time'); ?>" name="<?php echo $this->get_field_name('exclude_start_time'); ?>" type="time" value="<?php echo esc_attr($exclude_start_time); ?>">
                </p>
    
                <p>
                    <label for="<?php echo $this->get_field_id('exclude_end_date'); ?>"><?php _e('終了日:', 'text_domain'); ?></label>
                    <input class="widefat" id="<?php echo $this->get_field_id('exclude_end_date'); ?>" name="<?php echo $this->get_field_name('exclude_end_date'); ?>" type="date" value="<?php echo esc_attr($exclude_end_date); ?>">
                </p>
    
                <p>
                    <label for="<?php echo $this->get_field_id('exclude_end_time'); ?>"><?php _e('終了時間:', 'text_domain'); ?></label>
                    <input class="widefat" id="<?php echo $this->get_field_id('exclude_end_time'); ?>" name="<?php echo $this->get_field_name('exclude_end_time'); ?>" type="time" value="<?php echo esc_attr($exclude_end_time); ?>">
                </p>
            </div>
    
            <script>
                document.addEventListener("DOMContentLoaded", function() {
                    var checkbox = document.getElementById("<?php echo $this->get_field_id('enable_exclude'); ?>");
                    var excludeSettings = document.getElementById("<?php echo $this->get_field_id('exclude_settings'); ?>");
    
                    function toggleExcludeSettings() {
                        excludeSettings.style.display = checkbox.checked ? "block" : "none";
                    }
    
                    checkbox.addEventListener("change", toggleExcludeSettings);
                });
            </script>
    
            <?php
        }
    
        public function update($new_instance, $old_instance) {
            $instance = [];
            $instance['title'] = (!empty($new_instance['title'])) ? strip_tags($new_instance['title']) : '';
            $instance['content'] = (!empty($new_instance['content'])) ? $new_instance['content'] : '';
            $instance['visibility'] = (!empty($new_instance['visibility'])) ? strip_tags($new_instance['visibility']) : 'show';
            $instance['start_date'] = (!empty($new_instance['start_date'])) ? strip_tags($new_instance['start_date']) : '';
            $instance['end_date'] = (!empty($new_instance['end_date'])) ? strip_tags($new_instance['end_date']) : '';
            $instance['start_time'] = (!empty($new_instance['start_time'])) ? strip_tags($new_instance['start_time']) : '00:00';
            $instance['end_time'] = (!empty($new_instance['end_time'])) ? strip_tags($new_instance['end_time']) : '23:59';
            $instance['exclude_start_date'] = (!empty($new_instance['exclude_start_date'])) ? strip_tags($new_instance['exclude_start_date']) : '';
            $instance['exclude_end_date'] = (!empty($new_instance['exclude_end_date'])) ? strip_tags($new_instance['exclude_end_date']) : '';
            $instance['exclude_start_time'] = (!empty($new_instance['exclude_start_time'])) ? strip_tags($new_instance['exclude_start_time']) : '00:00';
            $instance['exclude_end_time'] = (!empty($new_instance['exclude_end_time'])) ? strip_tags($new_instance['exclude_end_time']) : '23:59';
            $instance['days'] = (!empty($new_instance['days'])) ? $new_instance['days'] : [0, 1, 2, 3, 4, 5, 6];
            
            return $instance;
        }
    }
    function register_Scheduled_widget() {
        register_widget('Scheduled_Widget');
    }
    add_action('widgets_init', 'register_Scheduled_widget');

    期間の指定がない場合を考慮し、開始期間、終了期間、時間等はディフォルトであらかじめ設定していますので問題があるようでしたら変更ください。
    曜日の並びについては、ワードプレス設定の曜日始まりに合わせたものとなっています。
    また、日時はサーバー時間を基準としています。

    修正箇所

    すでに上に記載されてPHPの内容は修正済みとなっていますが、修正箇所だけを直すのであれば下記となります。
    37行目あたりにあるショートコード対応の部分

    // ショートコード対応
    echo '<p>' . do_shortcode($content) . '</p>';
    echo $args['after_widget']; echo $args['after_widget'];
    }

    を下記のように修正しています。

      // ショートコード出力時に <p> タグを付けない
        echo do_shortcode($content);
        echo $args['after_widget']; // ← ここで1回だけ出力
    }

    不要な<p>タッグが入っていたためにレイアウト崩れが発生していたようです。

    使い方

    説明するほどではありませんがとりあえず...。
    Function.phpにコードを張り付けるとウィジェット管理画面に【Scheduled Widget】が表示されます。

    Widget1

    【Scheduled Widget】をサイドバーなどに設置し設定をしていきます。

    Widget2
    Widget2
    widget3
    widget3
    Widget2
    widget3

    ① タイトル
    ② 表示したいコンテンツ、HTML、ショートコード等、ウィンドウの大きさ変更可能
    ③ 表示 / 非表示の選択
    ④ 開始日時指定
    ⑤ 終了日時指定
    ⑥ 対象曜日指定
    ⑦ 除外期間設定 あり/なし選択
    ⑧ 除外期間 開始日時指定
    ⑨ 除外期間 終了日時指定
    除外期間の設定については、⑦の除外期間設定のチェックを入れると表示されます。
    チェックを入れても表示されない場合には画面をリフレッシュすると表示されます。

    注意事項

    キャッシュ関連のプラグインを使用している場合は、キャッシュが更新されないと期間指定がうまく作動しませんので注意が必要です。
    WP Fastest Cache プラグインを使用している場合は下記記事をご参考ください。

    さいごに

    Scheduled Widgeについてですが、全ての環境で機能するかどうかは確認していませんのでご了承ください。
    また、他のプラグイン等と競合する場合もあるかもしれませんので、Function.phpに追加する際は、バックアップを行うなど自己責任で実施ください。

    ? 元の記事を見る

    投稿や固定ページをプラグインなしで複製する方法を実装してみた。

    以前は、投稿記事や固定ページを複製する際にプラグインを使用していました。しかし、複製機能の使用頻度がそれほど高くないと感じたため、プラグインを削除しました。
    その後、お知らせなどの記事を再利用したい場面が出てきたため、自前で記事の複製機能を追加することにしました。今回は、プラグインに頼らず、functions.php にコードを記述して機能を実装する方法を選びました。
    機能はシンプルで、「複製」のみを目的としたものですが、投稿記事、固定ページ、そしてカスタム投稿にも対応できるようになっています。
    同じような機能の実装を検討している方の参考になればと思い、忘備録としてこの記事を残します。

    記事の複製 機能について

    特に変わった機能はなく、シンプルにクリックするだけで複製されるものです。
    複製された記事は、すぐに編集画面が開き、タイトルの前に複製の文字が投入されます。

    Function.phpに貼付け

    下記コードをFunction.phpに貼り付ければ完成です。
    16行目の青色の”複製:”がタイトルの前に記述されます。
    56行目の”記事を複製”が投稿一覧のオプション(編集、クイック編集等があるところ)に表示されます。
    複製、記事を複製はお好きな言葉に変更ください。

    /******************************
    投稿記事 複製機能の追加
    *****************************/
    function duplicate_post_as_draft(){
        global $wpdb;
    
        if (! (isset($_GET['post']) || isset($_POST['post']) || (isset($_REQUEST['action']) && 'duplicate_post_as_draft' == $_REQUEST['action']))) {
            wp_die('No post to duplicate has been supplied!');
        }
    
        $post_id = (isset($_GET['post']) ? $_GET['post'] : $_POST['post']);
        $post = get_post($post_id);
    
        if (isset($post) && $post != null) {
            // 新しい投稿タイトルに「複製」を追加
            $new_title = '複製: ' . $post->post_title;
    
            $new_post = array(
                'post_title' => $new_title, // ここでタイトルに「複製」を追加
                'post_content' => $post->post_content,
                'post_status' => 'draft',  // 新しい投稿を下書きとして作成
                'post_type' => $post->post_type,
                'post_author' => $post->post_author,
            );
    
            $new_post_id = wp_insert_post($new_post);
    
            // タクソノミー(カテゴリ、タグなど)を複製
            $taxonomies = get_object_taxonomies($post->post_type);
            foreach ($taxonomies as $taxonomy) {
                $post_terms = wp_get_object_terms($post_id, $taxonomy, array('fields' => 'slugs'));
                wp_set_object_terms($new_post_id, $post_terms, $taxonomy, false);
            }
    
            // メタデータを複製
            $post_meta_infos = $wpdb->get_results("SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id=$post_id");
            if (count($post_meta_infos) != 0) {
                foreach ($post_meta_infos as $meta_info) {
                    $meta_key = $meta_info->meta_key;
                    $meta_value = $meta_info->meta_value;
                    update_post_meta($new_post_id, $meta_key, $meta_value);
                }
            }
    
            // 新しい投稿の編集画面にリダイレクト
            wp_redirect(admin_url('post.php?action=edit&post=' . $new_post_id));
            exit;
        } else {
            wp_die('Post creation failed, could not find original post.');
        }
    }
    add_action('admin_action_duplicate_post_as_draft', 'duplicate_post_as_draft');
    
    function duplicate_post_link($actions, $post) {
        if (current_user_can('edit_posts')) {
            $actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=duplicate_post_as_draft&post=' . $post->ID, basename(__FILE__), 'duplicate_nonce') . '" title="Duplicate this item" rel="permalink">記事を複製</a>';
        }
        return $actions;
    }
    add_filter('post_row_actions', 'duplicate_post_link', 10, 2);
    add_filter('page_row_actions', 'duplicate_post_link', 10, 2); // 固定ページにも適用

    問題がなければ⇩のようになっているはずです。

    Dupulicate1
    Dupulicate2

    通常は上のコードで、投稿、固定、カスタム投稿で問題なくオプションが表示されると思います。
    もしカスタム投稿タイプで”記事の複製”のオプションが表示されない場合、一番下に下記を追加してみてください。

    add_filter('投稿タイプ名_row_actions', 'duplicate_post_link', 10, 2); // カスタム投稿タイプに適用

    赤色の投稿タイプ名を設定しているカスタム投稿タイプ名に変更ください。

    さいごに

    とってもシンプルな機能ですが、いがいに役に立ちますよ。
    興味のある方はお試しください。

    Function.phpの編集を行う際には、バックアップ等を取ることをお勧めします。
    編集にあたっては、自己責任となります。

    ? 元の記事を見る

    目次
    タイトルとURLをコピーしました