【スポンサーリンク】

WordPressのカスタムプラグインのエラー(get_postsのメモリ不足)

WordPressのカスタムプラグインのエラー(get_postsのメモリ不足)
閲覧中のユーザー数
(閲覧中のユーザー:0)

自作プラグインが動かなくなった原因は、サーバー処理のメモリ不足でした。

    $posts = get_posts( array(
    'post_type' => 'post',
    'post_status' =>  array( 'publish', 'pending', 'draft', 'future'),
    'orderby' => 'modified', /* date, modified */
    'numberposts' => -1
    ) );

    $count = 0;
    foreach ( $posts as $post ) { 
        $count += post_word_count($post->post_content);  
    }

get_posts関数で、全記事の文字数を計算するときに、メモリ不足になっていました。

WordPressのカスタムプラグインのエラー(get_postsのメモリ不足)

get_postsのメモリ管理が変わったのか、記事数が増えたことが原因なのかはわかりません。

そこで、バッチ処理に分割して、文字数を合計していくように変更しました。

/** メモリ消費を抑えるために、以下のようにバッチ処理を実装 */
function calc_total_post_characters() {
    global $wpdb;
    $total_count = 0;
    $batch_size = 100; // 一度に処理する投稿数
    $offset = 0;

    do {
        $query = $wpdb->prepare(
            "SELECT ID, post_content
             FROM {$wpdb->posts}
             WHERE post_type = %s
             AND post_status IN (%s, %s, %s, %s)
             ORDER BY ID
             LIMIT %d OFFSET %d",
            'post',
            'publish',
            'pending',
            'draft',
            'future',
            $batch_size,
            $offset
        );

        $posts = $wpdb->get_results($query);

        foreach ($posts as $post) {
            $total_count += post_word_count($post->post_content);
        }

        $offset += $batch_size;

        // メモリをクリア
        wp_cache_flush();
        if (function_exists('opcache_reset')) {
            opcache_reset();
        }

    } while (count($posts) == $batch_size);

    return $total_count;
}

そのほか、変数の定義やエラーチェックを見直しました。

\記事が役に立ったらシェアしてね/
【スポンサーリンク】

1. 自作プラグインが表示されない

WordPressを開いたら、カスタムプラグインが動作しなくなっていました。

自作プラグインが表示されない

ちょうど最近 WordPress 6.6になったからか、PHP 8.2に上がっていたからか、原因がわかりませんが、プラグインの動作の修正が必要なようです。

自作プラグインが表示されない

1-1. WordPressとPHPのエラーメッセージ

WordPressのデバッグモードを有効にする1と、エラーメッセージが出て来ました。

WordPressとPHPのエラーメッセージ
  • Warning:
    Constant ACCESSES_TABLE_NAME already defined
    in access-func.php
  • Warning:
    Cannot modify header information
    – headers already sent by (output started at access-func.php:12)
    in misc.php on line 1438
  • Warning:
    Cannot modify header information
    – headers already sent by (output started at access-func.php:12)
    in functions.php on line 7108
  • Warning:
    Cannot modify header information
    – headers already sent by (output started at access-func.php:12)
    in admin-header.php on line 9
  • Warning:
    Undefined variable $row
    in chiilabo-stats-main.php on line 376
  • Warning:
    Undefined variable $row
    in chiilabo-stats-main.php on line 376
  • Fatal error:
    Allowed memory size of 268435456 bytes exhausted
    (tried to allocate 20480 bytes)
    in meta.php on line 1180
Warning: Constant ACCESSES_TABLE_NAME already defined in /home/[username]/public_html/chiilabo.co.jp/wp-content/themes/cocoon-master/lib/page-access/access-func.php on line 12

Warning: Cannot modify header information - headers already sent by (output started at /home/[username]/public_html/chiilabo.co.jp/wp-content/themes/cocoon-master/lib/page-access/access-func.php:12) in /home/[username]/public_html/chiilabo.co.jp/wp-admin/includes/misc.php on line 1438

Warning: Cannot modify header information - headers already sent by (output started at /home/[username]/public_html/chiilabo.co.jp/wp-content/themes/cocoon-master/lib/page-access/access-func.php:12) in /home/[username]/public_html/chiilabo.co.jp/wp-includes/functions.php on line 7108

Warning: Cannot modify header information - headers already sent by (output started at /home/[username]/public_html/chiilabo.co.jp/wp-content/themes/cocoon-master/lib/page-access/access-func.php:12) in /home/[username]/public_html/chiilabo.co.jp/wp-admin/admin-header.php on line 9

Warning: Undefined variable $row in /home/[username]/public_html/chiilabo.co.jp/wp-content/plugins/chiilabo-stats/chiilabo-stats-main.php on line 376

Warning: Undefined variable $row in /home/[username]/public_html/chiilabo.co.jp/wp-content/plugins/chiilabo-stats/chiilabo-stats-main.php on line 380

Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 20480 bytes) in /home/[username]/public_html/chiilabo.co.jp/wp-includes/meta.php on line 1180

このサイトで重大なエラーが発生しました。対応手順については、サイト管理者のメール受信ボックスを確認してください。

メールでもPHPエラーが届いていました。
PHPのスクリプトエンジンが出力するエラーは、メールで送る設定になっています。

  • Uncaught ArgumentCountError:
    Too few arguments to function echo_grid_stats_display_style(),
    0 passed
    in chiilabo-stats-main.php on line 553
    and exactly 1 expected
    in chiilabo-stats-main.php:368
エラー詳細
===============
エラータイプ E_ERROR が /home/[username]/public_html/chiilabo.co.jp/wp-content/plugins/chiilabo-stats/chiilabo-stats-main.php ファイルの 368 行目で発生しました。 

エラーメッセージ: Uncaught ArgumentCountError: Too few arguments to function echo_grid_stats_display_style(), 0 passed in /home/[username]/public_html/chiilabo.co.jp/wp-content/plugins/chiilabo-stats/chiilabo-stats-main.php on line 553 and exactly 1 expected in /home/[username]/public_html/chiilabo.co.jp/wp-content/plugins/chiilabo-stats/chiilabo-stats-main.php:368

Stack trace:
#0 /home/[username]/public_html/chiilabo.co.jp/wp-content/plugins/chiilabo-stats/chiilabo-stats-main.php(553): echo_grid_stats_display_style()
#1 /home/[username]/public_html/chiilabo.co.jp/wp-content/plugins/chiilabo-stats/chiilabo-stats-main.php(358): grid_stats_display_inner(180)
#2 /home/[username]/public_html/chiilabo.co.jp/wp-includes/class-wp-hook.php(324): grid_stats_display('')
#3 /home/[username]/public_html/chiilabo.co.jp/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters('', Array)
#4 /home/[username]/public_html/chiilabo.co.jp/wp-includes/plugin.php(517): WP_Hook->do_action(Array)
#5 /home/[username]/public_html/chiilabo.co.jp/wp-admin/admin.php(259): do_action('%e3%81%a1%e3%81...')
#6 {main}
 thrown

ただし、WP_DEBUGがオンのままだと、サイト訪問者にも表示されてしまいます。

WordPressとPHPのエラーメッセージ

エラーメッセージを確認したら、そのつど すぐにオフに戻しました。

1-2. エラーメッセージの概要

コードの不注意に対する警告とメモリ不足のエラーがありました。

  • 定数の重複定義
    ACCESSES_TABLE_NAMEという定数が二度定義されているという警告が出ています。
  • 未定義の変数
    $rowという変数が定義されていないという警告が2回出ています。
  • ヘッダー情報の変更エラー
    ヘッダー情報を変更できないという警告が3回出ています。
    これはそれ以外の問題による二次的な問題で、ウェブページの内容を出力し始めた後で、ヘッダーにメッセージを出力しようとしたために起こっています。
  • メモリ不足エラー
    プログラムが使用できるメモリの上限を超えてしまったという重大なエラーが発生しています。
エラーメッセージの概要

いろんなファイルで警告が表示されていますが、自分が関係しているコードは chiilabo-stats-main.php と functions.php に限られています。
警告やエラーの示す場所にかかわらず、原因はその中にありそうです。

2. chiilabo-stats-main.phpの修正

このコードは、自分で追加したカスタムプラグインです。

2-1. 【修正】ACCESSES_TABLE_NAMEの再定義

まずは、ACCESSES_TABLE_NAME の問題を検証します。

最初のエラーは、ACCESSES_TABLE_NAME という定数が既に定義されているにも関わらず、再度定義しようとしていることを示しています。
/wp-content/themes/cocoon-master/lib/page-access/access-func.php ファイルの12行目)

しかし、Cocoonテーマではなく、自分のカスタムプラグインの問題です。
というのも、chiilabo-stats-main.php には、確かに ACCESSES_TABLE_NAME を定義する箇所が含まているからです。

if (!defined('ACCESSES_TABLE_NAME')) {
    define( 'ACCESSES_TABLE_NAME', $wpdb->prefix . 'cocoon' . '_accesses' );
}

これは、Cocoonテーマで記録したアクセスログを元に記事分析をしているためです。

再定義しないように気を付けてはいたのですが、「wp-content/themes/cocoon-master/lib/page-access/access-func.php」の方でエラーになっていました。

【修正】ACCESSES_TABLE_NAMEの再定義

読み込み順がプラグインの方がCocoonテーマより前になっているのでしょう。

そこで、access-func.phpと定数名が競合しないように、chiilabo-stats-main.php内の定数を「COCOON_ACCESSES_TABLE_NAME」という別名に変更しました。

【修正】ACCESSES_TABLE_NAMEの再定義

2-2. 【修正】リファクタリングのミス

次のエラーは、$rowの変数。
chiilabo-stats-main.php ファイルで $row 変数が使用される前に定義されていません。

これは、コードを整理するときにミスしていたことが原因でした。
関数の一部を別の関数に抜き出したときに、ローカル変数を含めたままだったのです。

ローカル変数は、パラメータとして渡すように修正しました。

これらの問題を修正すると同時に、ヘッダー情報の変更エラーも解消しました。

【修正】リファクタリングのミス

WordPressエラーの出力(echo文やHTMLなど)が、header()関数やセッション開始前に行われていたことが問題だったからです。

2-3. 【修正】Allowed memory size of 〜 bytes exhausted (get_posts)

残る問題が一番大きな問題です。
集計の表示で「致命的なエラー」になって停止してしまっているのです。

致命的なエラー: 許可されたメモリ サイズ 268435456 バイトを使い果たしました (20480 バイトを割り当てようとしました)

268435456 バイトは 256 MB に相当します。
スクリプトがさらに20480バイト(20 KB)のメモリを割り当てようとしたところで、限度に達していたためエラーが発生したようです。

wp-config.php ファイルに define('WP_MEMORY_LIMIT', '512M'); を追加するなど、メモリ制限を増やしてみたものの効果はありませんでした。

コード内にデバッグ出力を入れて、動作が停止している箇所を特定します。
すると、get_posts 関数で停止してしまっていることがわかりました。

function add_count_to_db() {
    $posts = get_posts( array(
    'post_type' => 'post',
    'post_status' =>  array( 'publish', 'pending', 'draft', 'future'),
    'orderby' => 'modified', /* date, modified */
    'numberposts' => -1
    ) );
    
    $count = 0;
    foreach ( $posts as $post ) { 
        $count += post_word_count($post->post_content);  
    }
    //...    

全記事の内容をまとめて取得する処理がメモリオーバーになっていたようです。

【修正】Allowed memory size of 〜 bytes exhausted (get_posts)

処理が遅いとは思っていたけど、今まで大丈夫だったのにね……

そこで、全記事の合計文字数を計算する処理を関数に分けて、100件ずつ取得して計算するように修正しました。

calc_total_post_characters()の動作
  • データベースから投稿を100件ずつバッチで取得します。
  • 各投稿の内容の文字数をカウントし、合計に加算します。
  • 全ての投稿を処理するまでこれを繰り返します。
  • 各バッチ処理後にキャッシュをクリアし、メモリ使用を最適化します。
/** メモリ消費を抑えるために、以下のようにバッチ処理を実装 */
function calc_total_post_characters() {
    global $wpdb;
    $total_count = 0;
    $batch_size = 100; // 一度に処理する投稿数
    $offset = 0;

    do {
        $query = $wpdb->prepare(
            "SELECT ID, post_content
             FROM {$wpdb->posts}
             WHERE post_type = %s
             AND post_status IN (%s, %s, %s, %s)
             ORDER BY ID
             LIMIT %d OFFSET %d",
            'post',
            'publish',
            'pending',
            'draft',
            'future',
            $batch_size,
            $offset
        );

        $posts = $wpdb->get_results($query);

        foreach ($posts as $post) {
            $total_count += post_word_count($post->post_content);
        }

        $offset += $batch_size;

        // メモリをクリア
        wp_cache_flush();
        if (function_exists('opcache_reset')) {
            opcache_reset();
        }

    } while (count($posts) == $batch_size);

    return $total_count;
}

function add_count_to_db() {
    $count = calc_total_post_characters();
    //...
    

3. functions.phpの見直し

一応、functions.phpについてもコードの問題点を検証しました。

3-1. 【廃止】PHPコードを実行するウィジェットwidget_text_exec_php

functions.phpに追加していた関数 widget_text_exec_php で出力バッファリングを使用していました。

/**
 * プラグインなしでPHPコードを実行する方法
 */
function widget_text_exec_php( $widget_text ) {
    if( strpos( $widget_text, '<' . '?' ) !== false ) {
        ob_start();
        eval( '?>' . $widget_text );
        $widget_text = ob_get_contents();
        ob_end_clean();
    }
    return $widget_text;
}
add_filter( 'widget_text', 'widget_text_exec_php', 99 );

この関数は、テキストウィジェットの表示前に実行され、テキストにPHPコードが含まれていれば、そのコードを評価(実行)した結果に置き換えて表示します。

しかし、eval()関数はセキュリティリスクが大きいです。

  • eval() 関数の使用:
    PHP 8.2に限らず、eval()の使用は非常に危険です。
    任意のPHPコードが実行される可能性があるからです。
  • 非推奨の関数 strpos():
    PHP 8.2では、strpos()関数の代わりにstr_contains()の使用が推奨されています。
  • 出力バッファリング ob_start()ob_end_clean():
    ob_start()ob_end_clean()の使用は、予期しない副作用を引き起こす可能性があります。
【廃止】PHPコードを実行するウィジェットwidget_text_exec_php

今は、ウィジェットテキストでPHPコードを使っていなかったので、丸ごと削除することにしました。

3-2. 【検討】アクセスランキング取得関数 get_trend_ranking_recordsの処理

関数 get_trend_ranking_records 内でもデータベースクエリを実行しています。
これも、メモリ使用量が多くなる可能性があります。

以下のようなショートコードのための関数です。

  
「接続に問題があるか、MMIコードが正しくありません。」【MMIコードとSIM、APN】
「接続に問題があるか、MMIコードが正しくありません。」【MMIコードとSIM、APN】
iCloud解析・iPhone解析を「Appleと共有」しても大丈夫?
iCloud解析・iPhone解析を「Appleと共有」しても大丈夫?
[iPhone]ダウンロードしたファイルはどこにある?(iOSのファイル管理)
[iPhone]ダウンロードしたファイルはどこにある?(iOSのファイル管理)
急にスマホから警告音? 「クリーンアップしないと、携帯電話は廃止されます」という迷惑なアプリ広告
急にスマホから警告音? 「クリーンアップしないと、携帯電話は廃止されます」という迷惑なアプリ広告
WordやExcelの自動保存機能(Microsoft 365)
WordやExcelの自動保存機能(Microsoft 365)
Windows 11で突然 Bluetoothのオン・オフ ボタンが消えてしまった?
Windows 11で突然 Bluetoothのオン・オフ ボタンが消えてしまった?
AppCloudというアプリが「勝手に」アプリをインストールしようとする?
AppCloudというアプリが「勝手に」アプリをインストールしようとする?
if (!shortcode_exists('trend_list')) {
  add_shortcode('trend_list', 'trend_entries_shortcode');
}

このコードでは、Cocoonのアクセスランキングの関数の処理の最深部にあるSQLクエリだけを自分で指定しています。

    $query = "
	
SELECT t1.post_id, 
  ROUND((t2.today) / 1.5 - (t1.latest - t2.today) /  ($days - 2))  AS sum_count
FROM (
	SELECT {$trend_table}.post_id, 
	  SUM({$trend_table}.count) AS latest
	FROM {$trend_table}
	WHERE {$trend_table}.post_type = '$post_type'
	  AND {$trend_table}.date BETWEEN '$date_before' AND '$date' 
	GROUP BY {$trend_table}.post_id
) t1 INNER JOIN (
	SELECT {$trend_table}.post_id, 
	  SUM({$trend_table}.count) AS today
	FROM {$trend_table}
	WHERE {$trend_table}.post_type = '$post_type'
	  AND {$trend_table}.date BETWEEN '$date_yesterday' AND '$date' 
	GROUP BY {$trend_table}.post_id
) t2
  ON t1.post_id = t2.post_id
ORDER BY  sum_count DESC
【検討】アクセスランキング取得関数 get_trend_ranking_recordsの処理

このクエリを使って記事リストを表示するためだけに、アクセスランキングのコード全体を複製しています。

このクエリでは、単純な全期間のアクセス数だけでなく、最近のトレンドも考慮に入れた人気度を計算しようとしています。

サブクエリ t1:
  • 指定された期間(’$date_before’から’$date’まで)の各投稿のアクセス数の合計を計算します。
  • これを ‘latest’ として保存します。
サブクエリ t2:
  • 昨日(’$date_yesterday’)から今日(’$date’)までの各投稿のアクセス数の合計を計算します。
  • これを ‘today’ として保存します。
メインクエリ:
  • t1 と t2 を post_id で結合します。
  • 各投稿について以下の計算を行います:
    ROUND((t2.today) / 1.5 – (t1.latest – t2.today) / ($days – 2)) AS sum_count
    今日の重み付けされたアクセス数から過去の平均を引くことで、最近の人気度を計算。
【検討】アクセスランキング取得関数 get_trend_ranking_recordsの処理

最近アクセスが増えている投稿は、全体的なアクセス数が少なくても上位に来る可能性があります。

function trend_entries_shortcode($atts) {
  extract(shortcode_atts(array(
    'days' => 'all',
    'count' => 5,
    'type' => 'default',
    'rank' => 0,
    'pv' => 0,
    'cats' => 'all',
    'children' => 0,
    'bold' => 0,
    'arrow' => 0,
    'class' => null,
    'author' => null,
    'post_type' => 'post',
    'horizontal' => 0,
  ), $atts, 'trend_list'));
  $cat_ids = array();
  if ($cats && $cats != 'all') {
    $cat_ids = explode(',', $cats);
  }
  $atts = array(
    'days' => $days,
    'entry_count' => $count,
    'entry_type' => $type,
    'ranking_visible' => $rank,
    'pv_visible' => $pv,
    'cat_ids' => $cat_ids,
    'children' => $children,
    'bold' => $bold,
    'arrow' => $arrow,
    'class' => $class,
    'author' => $author,
    'post_type' => $post_type,
    'horizontal' => $horizontal,
  );
  ob_start();
  generate_trend_entries_tag($atts);
  $res = ob_get_clean();
  return $res;
}
function generate_trend_entries_tag($atts){
  extract(shortcode_atts(array(
    'days' => 'all',
    'entry_count' => 5,
    'entry_type' => ET_DEFAULT,
    'ranking_visible' => 0,
    'pv_visible' => 0,
    'cat_ids' => array(),
    'children' => 0,
    'exclude_post_ids' => array(),
    'exclude_cat_ids' => array(),
    'bold' => 0,
    'arrow' => 0,
    'class' => null,
    'author' => null,
    'post_type' => 'post',
    'horizontal' => 0,
  ), $atts));

  $records = get_trend_ranking_records($days, $entry_count, $entry_type, $cat_ids, $exclude_post_ids, $exclude_cat_ids, $children, $author, $post_type);

  $thumb_size = get_popular_entries_thumbnail_size($entry_type);
  $atts = array(
    'type' => $entry_type,
    'ranking_visible' => $ranking_visible,
    'pv_visible' => $pv_visible,
    'bold' => $bold,
    'arrow' => $arrow,
    'class' => $class,
    'horizontal' => $horizontal,
  );
  $cards_classes = get_additional_widget_entry_cards_classes($atts);
  ?>
  <div class="popular-entry-cards widget-entry-cards no-icon cf<?php echo $cards_classes; ?>">
  <?php if ( $records ) :
    $i = 1;
    foreach ($records as $post):
      $permalink = get_permalink( $post->ID );
      $title = $post->post_title;
      $no_thumbnail_url = ($entry_type == ET_DEFAULT) ? get_no_image_120x68_url($post->ID) : get_no_image_320x180_url($post->ID);
      $w   = ($entry_type == ET_DEFAULT) ? THUMB120WIDTH  : THUMB320WIDTH;
      $h   = ($entry_type == ET_DEFAULT) ? THUMB120HEIGHT : THUMB320HEIGHT;

      $post_thumbnail = get_the_post_thumbnail( $post->ID, $thumb_size, array('alt' => '', 'loading' => 'lazy', 'decoding' => 'async') );
      $pv = $post->sum_count;

      if ($post_thumbnail) {
        $post_thumbnail_img = $post_thumbnail;
      } else {
        $post_thumbnail_img = get_original_image_tag($no_thumbnail_url, $w, $h, 'no-image popular-entry-card-thumb-no-image widget-entry-card-thumb-no-image', '');
      }

      $pv_tag = null;
      if ($pv_visible){
        $pv_text = $pv == '1' ? $pv.' view' : $pv.' views';
        $pv_tag = '<span class="popular-entry-card-pv widget-entry-card-pv">'.$pv_text.'</span>';
      }
      ?>
  <a href="<?php echo $permalink; ?>" class="popular-entry-card-link widget-entry-card-link a-wrap no-<?php echo $i; ?>" title="<?php echo esc_attr($title); ?>">
    <div class="popular-entry-card widget-entry-card e-card cf">
      <figure class="popular-entry-card-thumb widget-entry-card-thumb card-thumb">
        <?php echo $post_thumbnail_img; ?>
        <?php
        $is_visible = apply_filters('is_popular_entry_card_category_label_visible', false);
        $is_visible = apply_filters('is_widget_entry_card_category_label_visible', $is_visible);
        the_nolink_category($post->ID, $is_visible); //カテゴリラベルの取得 ?>
      </figure><!-- /.popular-entry-card-thumb -->

      <div class="popular-entry-card-content widget-entry-card-content card-content">
        <span class="popular-entry-card-title widget-entry-card-title card-title"><?php echo $title;?></span>
        <?php if ($entry_type != ET_LARGE_THUMB_ON): ?>
          <?php echo $pv_tag; ?>
        <?php endif ?>
        <?php generate_widget_entry_card_date('popular', $post->ID); ?>
      </div><!-- /.popular-entry-content -->
      <?php if ($entry_type == ET_LARGE_THUMB_ON): ?>
        <?php echo $pv_tag; ?>
      <?php endif ?>
    </div><!-- /.popular-entry-card -->
  </a><!-- /.popular-entry-card-link -->

  <?php
  $i++;
  endforeach;
  else :
    echo '<p>'.__( '人気記事は見つかりませんでした。', THEME_NAME ).'</p>';//見つからない時のメッセージ
  endif; ?>
  </div>
<?php
}
function get_trend_ranking_records($days = 'all', $limit = 5, $type = ET_DEFAULT, $cat_ids = array(), $exclude_post_ids = array(), $exclude_cat_ids = array(), $children = 0, $author = null, $post_type = 'post'){
  //カテゴリー配列を文字列に変換
  $cat_ids = is_array($cat_ids) ? $cat_ids : array();
  $cats = implode(',', $cat_ids);
  
  //アクセスキャッシュを有効にしている場合
  if (is_access_count_cache_enable()) {
    if ($cat_ids) {
      //子孫カテゴリも含める場合
      if ($children) {
        $categories = $cat_ids;
        $res = $categories;
        foreach ($categories as $category) {
          $res = array_merge($res, get_term_children( $category, 'category' ));
        }
        $cat_ids = $res;
        $cats = implode(',', $res);
      }
    }

    //除外投稿
    $archive_exclude_post_ids = get_archive_exclude_post_ids();
    if ($archive_exclude_post_ids && is_array($archive_exclude_post_ids)) {
      $exclude_post_ids = array_unique(array_merge($exclude_post_ids, $archive_exclude_post_ids));
    }

    $exclude_post_ids = is_array($exclude_post_ids) ? $exclude_post_ids : array();
    $expids = implode(',', $exclude_post_ids);
    $exclude_cat_ids = is_array($exclude_cat_ids) ? $exclude_cat_ids : array();
    $excats = implode(',', $exclude_cat_ids);
    $type = get_accesses_post_type();
    $transient_id = TRANSIENT_POPULAR_PREFIX.'?days='.$days.'&limit='.$limit.'&type='.$type.'&cats='.$cats.'&children='.$children.'&expids='.$expids.'&excats='.$excats.'&author='.$author.'&post_type='.$post_type;
    
    $cache = get_transient( $transient_id );
    if ($cache) {
      if (DEBUG_MODE && is_user_administrator()) {
        // echo('<pre>');
        // echo $transient_id;
        // echo('</pre>');
      } elseif (is_user_administrator()){

      } else {
        return $cache;
      }
    }
  }



  global $wpdb;
  $trend_table = ACCESSES_TABLE_NAME;
  // $post_type = 'post';
  $date = get_current_db_date();
  $date_yesterday = get_current_db_date_before(1);

  $where = " WHERE {$trend_table}.post_type = '$post_type' ".PHP_EOL;
  // _v($where);
  if ($days != 'all') {
    $date_before = get_current_db_date_before($days);
    $where .= " AND {$trend_table}.date BETWEEN '$date_before' AND '$date' ".PHP_EOL;
  }

  if (is_ids_exist($exclude_post_ids)) {
    $where .= " AND {$trend_table}.post_id NOT IN(".implode(',', $exclude_post_ids).") ".PHP_EOL;
  }
  //3180, 3234
  if (!is_numeric($limit)) {
    $limit = 5;
  }
  //カテゴリを指定する場合
  if (is_ids_exist($cat_ids) || is_ids_exist($exclude_cat_ids)) {
    global $post;
    $term_relationships = $wpdb->term_relationships;
    $term_taxonomy = $wpdb->term_taxonomy;
    $joined_table = 'terms_accesses';
    //カテゴリー指定
    if (is_ids_exist($cat_ids)) {
      $cat_ids = implode(',', $cat_ids);
      //$where .= " AND {$term_relationships}.term_taxonomy_id IN ({$cat_ids}) ".PHP_EOL;
      $where .= " AND {$term_taxonomy}.term_id IN ({$cat_ids}) ".PHP_EOL;
    }
    //除外カテゴリー指定
    if (is_ids_exist($exclude_cat_ids)) {
      //空の配列を取り除く
      $exclude_cat_ids = array_filter($exclude_cat_ids, "strlen");
      //カンマ区切りにする
      $ex_cat_ids = implode(',', $exclude_cat_ids);
      $ex_cat_ids = preg_replace('/,$/', '', $ex_cat_ids);
      $where .= " AND {$term_relationships}.term_taxonomy_id NOT IN ({$ex_cat_ids}) ".PHP_EOL;
    }

    $where .= " AND {$term_taxonomy}.taxonomy = 'category' ".PHP_EOL;
    // //テーブル結合するクエリの場合はWHEREに付け加えるのでANDに変更する
    // $where = str_replace('WHERE', 'AND', $where);
    $query = "
      SELECT {$joined_table}.post_id, SUM({$joined_table}.count) AS sum_count, {$joined_table}.term_taxonomy_id, {$joined_table}.taxonomy
        FROM (
          #カテゴリとアクセステーブルを内部結合してグルーピングし並び替えた結果
          SELECT {$trend_table}.post_id, {$trend_table}.count, {$term_relationships}.term_taxonomy_id, {$term_taxonomy}.taxonomy
            FROM {$term_relationships}
            INNER JOIN {$trend_table} ON {$term_relationships}.object_id = {$trend_table}.post_id
            INNER JOIN {$term_taxonomy} ON {$term_relationships}.term_taxonomy_id = {$term_taxonomy}.term_taxonomy_id
            $where #WHERE句
            GROUP BY {$trend_table}.id
        ) AS {$joined_table} #カテゴリとアクセステーブルを内部結合した仮の名前
        GROUP BY {$joined_table}.post_id
        ORDER BY sum_count DESC
    ";
    //_v($query);
    //1回のクエリで投稿データを取り出せるようにテーブル結合クエリを追加
    $query = wrap_joined_wp_posts_query($query, $limit, $author, $post_type);
  } else {
//  ROUND((t2.today)- (t1.latest - t2.today) /  ($days - 2))  AS sum_count
	  
    $query = "
	
SELECT t1.post_id, 
  ROUND((t2.today) / 1.5 - (t1.latest - t2.today) /  ($days - 2))  AS sum_count
FROM (
	SELECT {$trend_table}.post_id, 
	  SUM({$trend_table}.count) AS latest
	FROM {$trend_table}
	WHERE {$trend_table}.post_type = '$post_type'
	  AND {$trend_table}.date BETWEEN '$date_before' AND '$date' 
	GROUP BY {$trend_table}.post_id
) t1 INNER JOIN (
	SELECT {$trend_table}.post_id, 
	  SUM({$trend_table}.count) AS today
	FROM {$trend_table}
	WHERE {$trend_table}.post_type = '$post_type'
	  AND {$trend_table}.date BETWEEN '$date_yesterday' AND '$date' 
	GROUP BY {$trend_table}.post_id
) t2
  ON t1.post_id = t2.post_id
ORDER BY  sum_count DESC

	";
    //1回のクエリで投稿データを取り出せるようにテーブル結合クエリを追加
    $query = wrap_joined_wp_posts_query($query, $limit, $author, $post_type);
  }

  $records = $wpdb->get_results( $query );
  
  if (is_access_count_cache_enable() && $records) {
    set_transient( $transient_id, $records, 60 * get_access_count_cache_interval() );
  }
  return $records;
}
endif;
【検討】アクセスランキング取得関数 get_trend_ranking_recordsの処理

もう少し短いコードで解決できそうですが、今回は見送りです。

こちらもどうぞ。
[WordPress] 投稿月でグループ分けした月別PV集計表を見るためのカスタムプラグインを作った【ChatGPTと】
[WordPress] 投稿月でグループ分けした月別PV集計表を見るためのカスタムプラグインを作った【ChatGPTと】
一ヶ月分のブログ公開がどれぐらいのページビューにつながっているのか、集計する自分用のWordPressプラグインを作成しました。自分にとっては未経験の分野でしたが、対話型AI「ChatGPT」に相談してみると、使える叩き台を用意してくれました。WordPressプラグインづくりははじめてでしたが、かなりの時間短縮。無事に完成しました。アクセス集計プラグイン一ヶ月分のブログ公開が、どれぐらいの成果になっているのか知りたいことがあります。これまでは、Google Analytic...

WordPressカスタムプラグインのタイムアウトを避ける(mixhost)
WordPressカスタムプラグインのタイムアウトを避ける(mixhost)
カスタムプラグインでサイトの統計情報を分析しているのですが、時々タイムアウトで動かなくなってしまうんです。そこで、cPanelのMultiPHP INIエディタを使って、max_execution_timeの設定を変更してみました。これで、プラグインが途中で止まらずに済むようになりました。カスタムプラグインの動作が安定しないカスタムプラグインでサイト統計情報を分析しています。ところが、最近 そのプラグインの統計ページが動かないときがあります。ページの再読み込みすると動いたりも...

「サイトで重大なエラーが発生しました」(ゼロ除算の2つの結果)
「サイトで重大なエラーが発生しました」(ゼロ除算の2つの結果)
PHPのバージョンを7.4から8.0に上げたら、カスタムプラグインがエラーになるようになりました。エラーをみると「ゼロ除算(DivisionByZeroError)」。これまでは除算後に NaN のチェックをしていたのですが、除算前に 0 のチェックが必要になったみたいです。どうも、除算演算子で fdiv でなく intdiv が使われるようになっていたのが原因ようです。カスタムプラグインのエラーPHPのバージョンを7.4から8.0に上げたら、以前に自分で作ったWordPre...

(補足)

  1. wp-config.php の WP_DEBUGをtrueに変更する –
QRコードを読み込むと、関連記事を確認できます。

WordPressのカスタムプラグインのエラー(get_postsのメモリ不足)
【スポンサーリンク】
タイトルとURLをコピーしました