20200521のPHPに関する記事は13件です。

PHP Deprecated: PHP Startup: Use of mbstring.internal_encoding is deprecated

PHP の開発バージョン(master/8.0.0-dev)で、mbstring を有効にすると mbstring.internal_encoding is deprecated と表示される。

$ docker run --rm -it keinos/php8-jit /bin/sh
/ $ php -v
PHP Deprecated:  PHP Startup: Use of mbstring.internal_encoding is deprecated in Unknown on line 0

Deprecated: PHP Startup: Use of mbstring.internal_encoding is deprecated in Unknown on line 0
PHP 8.0.0-dev (cli) (built: May 21 2020 15:58:54) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.0-dev, Copyright (c), by Zend Technologies

TL; DR

mbstring.internal_encoding は PHP 8.0.0 から削除されます。予定

mbstring.internal_encoding を設定している php.ini ファイルを探し、mbstring.internal_encoding の項目を削除(or ; でコメントアウト)する。

/usr/local/etc/php/conf.d/docker-php-enable-mb.ini
; Extended PHP.ini file to enable mbstring.
; =========================================
; Place this file under /usr/local/etc/php/conf.d/

zend.multibyte = On
zend.script_encoding = UTF-8
mbstring.language = Japanese
- mbstring.internal_encoding = UTF-8
+ ;mbstring.internal_encoding = UTF-8

TS; DR

もともと php.inimbstring.internal_encoding 設定は PHP 5.6.0 以降から非推奨でした。

master ブランチ(PHP 8.0.0-dev)のコミット 3ca08ee(2020/03/31)で削除され Deprecated となり、このまま差し戻されなければ PHP 8.0.0 の正式リリースで削除されます。

おそらく、mbstring.internal_encoding 以外の非推奨設定も PHP 8.0.0 を機に削除されるかもしれません。

2020/05/21 現在、非推奨とされる mbstring の設定項目。

名前 変更履歴 変更予定
mbstring.http_input PHP 5.6.0 で非推奨
mbstring.http_output PHP 5.6.0 で非推奨
mbstring.internal_encoding PHP 5.6.0 で非推奨 PHP 8.0.0 で削除予定(3ca08eeで削除)
mbstring.func_overload PHP 7.2.0 で非推奨
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC-CUBE4 で割引明細を追加するカスタマイズの例

EC-CUBE4 で割引明細を追加するカスタマイズの例

以下のPHPファイルを作成する。

app/Customize/Service/PurchaseFlow/Processor/TestDiscountProcessor.php

app/Customize/Service/PurchaseFlow/Processor/TestDiscountProcessor.php
<?php


namespace Customize\Service\PurchaseFlow\Processor;


use Doctrine\ORM\EntityManagerInterface;
use Eccube\Annotation\OrderFlow;
use Eccube\Annotation\ShoppingFlow;
use Eccube\Entity\ItemHolderInterface;
use Eccube\Entity\Master\OrderItemType;
use Eccube\Entity\Master\TaxDisplayType;
use Eccube\Entity\Master\TaxType;
use Eccube\Entity\OrderItem;
use Eccube\Service\PurchaseFlow\PurchaseContext;
use Eccube\Service\PurchaseFlow\DiscountProcessor;

/**
 * 値引きを追加する.
 *
 * ここでフローを指定
 * @ShoppingFlow
 * @OrderFlow
 */
class TestDiscountProcessor implements DiscountProcessor
{
    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

    /**
     * TestDiscountProcessor constructor.
     *
     * @param EntityManagerInterface $entityManager
     */
    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function removeDiscountItem(ItemHolderInterface $itemHolder, PurchaseContext $context)
    {
        foreach ($itemHolder->getItems() as $item) {
            if ($item->getProcessorName() == TestDiscountProcessor::class) {
                $itemHolder->removeOrderItem($item);
                $this->entityManager->remove($item);
            }
        }
    }

    public function addDiscountItem(ItemHolderInterface $itemHolder, PurchaseContext $context)
    {
        /* 必要であればここで適応条件の制御をする
         * if...
         */

        // 課税/不課税や税込/税抜はここで作成
        $DiscountType = $this->entityManager->find(OrderItemType::class, OrderItemType::DISCOUNT);
        $TaxInclude = $this->entityManager->find(TaxDisplayType::class, TaxDisplayType::EXCLUDED);
        $Taxation = $this->entityManager->find(TaxType::class, TaxType::TAXATION);

        $OrderItem = new OrderItem();
        $OrderItem->setProductName($DiscountType->getName())
            ->setPrice(-100)
            ->setQuantity(1)
            ->setTax(-10)
            ->setTaxRate(10)
            ->setRoundingType(null)
            ->setOrderItemType($DiscountType)
            ->setTaxDisplayType($TaxInclude)
            ->setTaxType($Taxation)
            ->setOrder($itemHolder)
            ->setProcessorName(TestDiscountProcessor::class);
        $itemHolder->addItem($OrderItem);
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WordPressでカスタマイズAPIを作る

WordPressの記事コンテンツをAPIとして書き出すには公式同等のWP REST APIが存在します。
しかし、セキュリティ面での懸念もあり、いろいろいらない情報が書き出されてしまったり、APIの構造が複雑になったり、いろいろデメリットがあります。
固定ページの機能を活用し、スラッグAPIの固定ページを作成し、テンプレートファイルにpage-api.phpファイルを作ってしまえば、カスタマイズしたAPIを簡単に作れちゃいます。
そのコードをご紹介します。

page-api.php
<?php
// ベーシック認証
switch (true) {
    case !isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']):
    //ベーシック認証のID・PWを設定
    case $_SERVER['PHP_AUTH_USER'] !== 'user_name':
    case $_SERVER['PHP_AUTH_PW']   !== 'pass_word':
        header('WWW-Authenticate: Basic realm="Enter username and password."', true, 401);
        header('Content-Type: text/plain; charset=utf-8');
        die('認証が必要です');
}
header('content-type: application/json; charset=utf-8');
header("Access-Control-Allow-Origin: *");

if($_SERVER["REQUEST_METHOD"] == "GET"){
  $api_data = '[';
  if(isset($_GET['cat_api'])) {//カテゴリ指定がある場合
    $cat_id = $_GET['cat_api'];
    $posts = wp_get_recent_posts(array( 'numberposts' => 10, 'post_status' => 'publish', 'category' => $cat_id ) );//公開済みの記事を対象に
  }else{//カテゴリ指定がない場合、全記事を対象に
    $posts = wp_get_recent_posts(array( 'numberposts' => 10, 'post_status' => 'publish,private' ) );
  }
  foreach($posts as $post){
    $post_id = $post["ID"];
    $category = get_the_category($post_id);
    $cat_name = $category[0]->cat_name;
    $thumbnail = get_the_post_thumbnail_url($post_id);
    $update = get_the_modified_date("Y-m-d H:i:s", $post_id);
    $status = get_post_status($post_id);
    $post_url = '{"url":' .  site_url() . $post["post_name"] . '/",';
    $post_image = '"image":' . '"' . $thumbnail .'",';
    $post_date = '"date":' . '"' . $post["post_date"] . '",';
    $post_update = '"update":' . '"' . $update . '",';
    $post_status = '"status":' . '"' . $status . '",';
    $post_category = '"category":' . '"' . $cat_name . '",';
    $post_title = '"title":' . '"' . $post["post_title"] . '",';
    $post_excerpt = '"expert":' . '"' . $post["post_excerpt"] . '",';
    $post_content = $post["post_content"];
    $post_content = wpautop($post_content); //余計な改行等々
    // 関連記事のデータ生成
    $relateds = wp_get_recent_posts(array( 'numberposts' => 3, 'post_status' => 'publish', 'category' => $category_id ) );
    $post_related = '<h2>関連記事</h2><ul>';
    foreach($relateds as $related){
        $related_id = $related["ID"];
        $post_related .= '<li><a href="' . site_url() . $related["post_name"] . '/">' . $related["post_title"] . '</li>';
    }
    $post_related .= '</ul>';
    $post_content .= $post_related;
    $post_content = str_replace('"', '\"', $post_content);
    $post_content = '"content":' . '"' . $post_content . '"},';
    $api_data = $api_data . $post_url . $post_image . $post_date . $post_update . $post_status . $post_category . $post_title . $post_excerpt . $post_content;
  }
  $api_data = $api_data . ']';
  $api_data = preg_replace('/(?:\n|\r|\r\n)/', '', $api_data ); //余計なものを一括置換
  $api_data = str_replace('},]', '}]', $api_data);
  $api_data = str_replace(' ', '', $api_data);
  print $api_data;
}else{
  header("HTTP/1.0 404 Not Found");
  return;
}


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WordPressのAMP対応をLazyLoadと両立

WordPressの記事コンテンツをAMP対応するために、いろいろプラグインがありますが、
無料のものだと、なかなかうまくいかず、予期せぬバグが発生したり、デザイン面のカスタマイズができなかったり、
いろいろと厳しい部分があります。
プラグインを使わずに、テンプレートファイルをカスタマイズすれば、意外にも簡単に対応できます。
こちらの記事で詳しく紹介されて、参考させていただきました。
WordPressをプラグインなしでAMP対応させる試み【その1:格闘編】

しかし、Lazyloadを使う場合、画像の一括置換でバッティングしてしまい、うまく動作しません。
そのために、functions.phpの該当箇所を両立できるように対応してみました。
ご参考まで!

functions.php
function add_image_placeholders( $content ) {
    if(isset($_GET['amp']) && is_single()){//ampの場合はampタグに置換
        $content = preg_replace(
        '#<img([^>]+?)src=[\'"]?([^\'"\s>]+)[\'"]?([^>]*)>#',
        sprintf( '<amp-img layout="responsive" src="${2}"${3}></amp-img>' ), $content );
        return $content;
    }elseif( is_feed() || is_preview() || ( function_exists( 'is_mobile' ) && is_mobile() ) ){//プレビューやフィードモバイルなどで遅延させない
        return $content;
    }elseif ( false !== strpos( $content, 'data-original' ) ){//既に適用させているところは処理しない
        return $content;
    }else{$content = preg_replace(//Lazyload画像正規表現で置換
        '#<img([^>]+?)src=[\'"]?([^\'"\s>]+)[\'"]?([^>]*)>#',//IMGタグの正規表現
        sprintf( '<img${1}src="%s" data-lazy="true" data-original="${2}"${3}><noscript><img${1}src="${2}"${3}></noscript>', get_template_directory_uri().'/images/lazy_image.gif' ),//置換するIMGタグ(JavaScriptがオフのとき用のnoscriptタグも追加)
        $content);//投稿本文(置換する文章)
        return $content;
    }
}
add_filter( 'the_content', 'add_image_placeholders', 99 );
add_filter( 'post_thumbnail_html', 'add_image_placeholders', 11 );

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerで構築したLaravel環境に、PHPStormでステップ実行デバッグを仕掛ける

元記事 Windows10 に PHP7+Laravel環境をDockerで構築
← 元元記事 最強のLaravel開発環境をDockerを使って構築する【新編集版】

目的

Dockerで構築したLaravel環境で実行されているPHPコードに対して、
JetBrains社の「PHPStorm」からステップ実行デバッグができるようにしたい。

ステップ実行デバッグとは

  • コードの好きな行で実行を停止させて、その瞬間の変数リストや変数の中身を覗き見する。
  • 1行ずつ実行させて、どこでエラー落ちするか追跡する。

といったことができる、例のアレ↓です。

image.png

概要

今回、PHP定番デバッグツール xdebug を使用して実現していきます。
(Laravel最新版には他にもイケてるデバッグツールがあると思いますが、諸事情で古いバージョンのLaravelを使用しなければならなかったので:sob:

PHPはサーバーサイドプログラミング言語なので、サーバー側に xdebug をインストールしてデバッグ情報を吐き出させるようにしないといけません。

加えて今回はWindowsやMacに構築したDockerコンテナの中でサーバーを動かしているので、ちょっとだけ工夫が必要です。

環境

  • 冒頭に書いた元記事および元元記事の手順でLaravel環境を構築済であること。
  • PHPStorm最新版を使って開発している方。(Windows版 / Mac版 両方で動作確認済。)
  • 開発環境や検証環境。(本番環境ではデバッグツールを入れる必要は無いので。)

方法

サーバー側作業

PHPサーバーに xdebug インストール&有効化する設定を追記する。

PHPサーバーは今回 Docker で構築されるようになっているので、その設定ファイルをいじります。
2行ほど追記します。

docker-laravel\infrastructure\docker\php\Dockerfile
・
・
・
RUN apt-get update && \
  apt-get -y install git libicu-dev libonig-dev libzip-dev unzip locales && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  locale-gen en_US.UTF-8 && \
  localedef -f UTF-8 -i en_US en_US.UTF-8 && \
  mkdir /var/run/php-fpm && \
  docker-php-ext-install intl pdo_mysql mbstring zip bcmath && \
  composer config -g process-timeout 3600 && \
  composer config -g repos.packagist composer https://packagist.org && \
# ↓↓↓ 追記 ↓↓↓
  pecl install xdebug && \
  docker-php-ext-enable xdebug && \
# ↑↑↑ 追記 ↑↑↑
  composer global require hirak/prestissimo
・
・
・

PHPサーバーの php.ini に xdebug 設定を追記する。

php.ini には「host.docker.internalというマシンからのデバッグ通信要求を受け付けてね」という設定を追加します。

host.docker.internalって、そんなアドレスのマシン持ってないぞ…?」
と思うかもですが、ご安心ください。
最近の Docker Desktop をお使いであれば、自動でそういう設定をしてくれます。

これはDockerホスト、つまりお手元の Windows や Mac を指し示すようになっています。
(Dockerコンテナの中からlocalhostとやるとDockerコンテナ自身を指してしまいDockerホストを辿れないので、こういう機能を用意してくれています。)

docker-laravel\infrastructure\docker\php\php.ini



[Assertion]
zend.assertions = 1

[mbstring]
mbstring.language = Japanese

# ↓↓↓ 追記 ↓↓↓
[xdebug]
xdebug.idekey="PHPStorm"
xdebug.remote_host = "host.docker.internal"
xdebug.default_enable = 1
xdebug.remote_autostart = 1
xdebug.remote_connect_back = 0
xdebug.remote_enable = 1
xdebug.remote_handler = "dbgp"
xdebug.remote_port = 9000
# ↑↑↑ 追記 ↑↑↑

Laravel環境を起動しておく。

これは元記事のまま、コマンド実行するだけです。

初めて実行する場合
cd docker-laravel/infrastructure
make create-project
すでにcreate-projectを実行したことがある場合
cd docker-laravel/infrastructure
make remake

コマンドが完了するまでお待ちください。
数分かかるかもです。

クライアント側作業(お手元のWindows/Mac)

PHPStormを起動する。

ここで初めて起動してください。
もしすでに起動してdocker-laravelプロジェクトを開いてしまっていたら、再起動してください。
(Dockerビルド工程でインポートされたPHPライブラリ群がインクルードされていない場合があるので。)

PHPStormで docker-laravel プロジェクトを開く。

image.png

メインメニューから設定画面を開く。

image.png

左メニューからLanguages & FrameworksPHPを開く。

image.png

画面右側の + を押す。

(ついでに、Include Path欄にPHPライブラリ群がずらーっとインクルードされているか念のため確認してください。)

image.png

docker-laravel を選択して OK を押す。

今回のプロジェクトのルートディレクトリを丸ごとパスに追加します。

image.png

左メニューからLanguages & FrameworksPHPDebugを開き、xdebugDebug port: 9000 にする。

最初から 9000 だったらそのままでOKです。
次へ進みましょう。

余談ですがなぜ 9000 かというと、今回PHPコードを処理するのはポート9000で待ち受けている php-fpm というソフトだからです。WEBアクセスそのものはポート80で待ち受けているWEBサーバーソフト nginx が処理しますが、PHP処理だけ php-fpm に投げる、という動きになります。
(ちょっと前のモダン構成だと、Apache というWEBサーバソフトがポート80で待ち受け、WEBもPHPも一括処理するというのが多かったです。)

image.png

左メニューからLanguages & FrameworksPHPServersを開き + を押す。

image.png

以下の設定をして、最後に Apply ボタンを押す。

設定項目
Name docker-laravel
Host localhost
Port 9000
Debugger Xdebug
User path mappings チェックON

image.png

終わったら閉じてOKです。

メインウィンドウ右上の Add Configuration... を押す。

image.png

左ツリー上部の + を押して PHP Remote Debug を選ぶ。

image.png

以下の設定をして、最後に Apply ボタンを押す。

設定項目
Name docker-laravel
Filter debug connection by IDE key チェックON
Server docker-laravel
IDE key PHPStorm

image.png

終わったら閉じてOKです。

メインウィンドウ右上の 緑色の昆虫 ボタンを押す。

image.png

そうすると、

  • 緑色の昆虫ボタンの右のほうに赤い■ボタンが点灯します。
  • メインウィンドウ下部にデバッグ情報小窓が展開されます。

image.png

これで、デバッグ中の状態になりました。
デバッグをやめるには赤い■ボタンを押せばOKです。

ブラウザでPHPサーバーにアクセスしてみる。

http://localhost/

image.png

普通なら Laravel のサンプルページが開くはずですが、ずっとアクセス中のように見えます。

実は、デバッガーが割り込んで処理を途中で止めているのです!

デバッグ情報小窓→Debuggerタブを開く。

お、デバッグ情報が来てますね!

ただ、何やらエラーが表示されています。
「サーバーコードと手元のソースファイルが関連付けられていないから追跡できないよ!」と怒られています。

なので、設定してあげましょう。
すぐ下の行に表示されている Click to set up path mappings を押して下さい。

image.png

以下の設定をして OK を押す。

  1. この画面、出現当初はすごくウィンドウが小さいので、広げてあげてください。
  2. Use path mappings にチェックを入れます。
  3. 下部にプロジェクトファイルリストが出てくるので、File path on server: に示されているコードに相当するソースファイルを指定してあげます。

image.png

再度PHPサーバーにアクセスしてみる。

http://localhost/

image.png

あれ、今度はすんなりWEBページが表示されて終わってしまいました。
でも大丈夫、正常です!

ソースコードの該当行にブレークポイントを仕掛けてみる。

このページのソースコードは index.php ですね。
では index.php を開いて、停止させてみたい行の冒頭をクリックしてみてください。
赤い●が点灯します。

image.png

再々度PHPサーバーにアクセスしてみる。

http://localhost/

image.png

来ました!
54行目で処理が一時停止され、その瞬間の変数などが丸見えです!

次のブレークポイントまで進めるなら
image.png

次の行、次の行、と一行ずつ進めていくなら
image.png

といったことも可能です。

それでは長くなってしまいましたが、今回はここまで。
お疲れ様でした:thumbsup:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

簡易的なKrunkerのチートシート(?)を作りました

サイト

https://katudon.000webhostapp.com/Home.php
途中まで攻略サイトみたいにしようと思ったんですが、そんなサイトは既に山ほど存在してるので、ダメージとかを計算できるデータベース的なサイトにしました。なので、銃一覧以外は機能しません。
いつかherokuに移すかも。

使用方法

見たい銃を銃一覧から選択するとその銃の性能を見ることができます。
スクリーンショット 2020-05-21 19.02.12.png

  • ドロップダウンメニュー(画像の"スナイパーライフル"って書いてるやつ)を選ぶことでその場で他の銃と比較が出来ます。左上の+を押すことで複数の銃と比較も可能。-を押すと一番右端のやつが消えます。

  • 威力のドロップダウンメニューから頭、胴、足それぞれのダメージを見ることが出来ます。

  • 総合DPSは勝手に自分が勝手に編み出したDPSで、下に小さく書いてある通り、人間の反応速度やエイム速度等を反映したDPSを計算します。

  • 総火力の所に数値を入れると(単位はms)、その間に与えることが出来るダメージの総数を計算します。

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ID重複例(雇用調整助成金の記事を見て)

はじめに

https://www3.nhk.or.jp/news/html/20200520/k10012437361000.html

雇用調整助成金オンライン申請に関する記事を見て、
じゃあ どんなときにIDが重複するのかを、私の経験を元にまとめました。

最大値+1

[果物マスタ]

ID 果物
1 りんご
2 みかん
3 いちご
: :
4 バナナ ( ← 同時に登録 )
4 ぶどう ( ← 同時に登録 )

この場合、最大値3+1、すなわち「4」が次のIDです。

しかし複数のプロセスが「同時」に最大値を取得すると、
両プロセスともに「4」という重複IDが割り振られてしまいます。
(一方が4、他方が5とはならない。)

コーディングにもよりますが、重複IDだけ割り振られて、
ぶどうの insert エラーをシステムが適切に処理しなかった場合、
ぶどうを登録したユーザに対して
「4:バナナ が正常に登録されました」
と、身に覚えのないメッセージが表示されるでしょう。

※「最大値+1」が悪いわけではなくて、
設計やコーディングが甘いことが問題です。

ランダム値

[果物マスタ]

ID 果物
13245 りんご
97860 みかん
82073 いちご
: :
56718 バナナ ( ← ランダム値が重複 )
56718 ぶどう ( ← ランダム値が重複 )

random_int() などで得られるランダム値をIDにする設計も、
たまに見かけます。これも重複IDが発生し得ます。

マスタを同時刻に登録しようとして、偶然、同じランダム値が
生成される可能性は、ゼロではありません。

IDの頭に「年月日時分秒」を付けても同じです。
同時刻に同じランダム値が生成されれば、意味がありません。

ID使いまわし + 未削除データ

前述の「最大値+1」の場合ですが、
「4:バナナ」を削除したら、次回 登録時、
再度「4」が割り振られてしまいます。

[果物マスタ(親)]

ID 果物
1 りんご
2 みかん
3 いちご
4 ぶどう ( ←「4:バナナ」を削除後 登録 )

[生産地マスタ(子)]

ID 果物
1 青森
2 和歌山
3 栃木
4 フィリピン ( ← 未削除データのため「山梨」を登録できない!)

このとき共通IDの別テーブルに、
例えば果物マスタの子テーブル「生産地マスタ」に、
バナナの生産地「4:フィリピン」が うっかり消されずに残っていたら、
ぶどうの生産地が登録できないかもしれません。

まとめ?

主キー、排他制御、トランザクション、AUTO_INCREMENT、シーケンス等を
システムに合わせて設計して、重複しないID生成を心がけましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP �foreachで配列の最初と最後に処理を追加する

環境

  • PHP 7.3 以上

コード

$items = [
    'tokyo' => '東京',
    'shibuya' => '渋谷',
    'harajuku' => '原宿',
    'yoyogi' => '代々木',
];

$keyFirst = array_key_first($items);
$keyLast = array_key_last($items);

foreach ($items as $key => $value) {
    if ($key === $keyFirst) {
        echo "$value is first" . PHP_EOL;
    }
    if ($key === $keyLast) {
        echo "$value is last" . PHP_EOL;
    }
}

実行結果

tokyo: 東京 is first
yoyogi: 代々木 is last

補足: 配列から最初と最後の値だけ取り出したい場合

echo "{$items[array_key_first($items)]} is first" . PHP_EOL;
echo "{$items[array_key_last($items)]} is last" . PHP_EOL;

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サーバから日本語を含むファイル名が正しく取得できない

先頭の日本語だけ消える?

sample.php
// (1)「file」というディレクトリ内のファイル一覧を配列形式で取得(「./file/ファイル名.jpg」というパス情報が取れる)
// 拡張子を指定する時はglob("./file/*.jpg")のようにする
$fileArray = glob("./file/*");

// (2)(1)で取得したファイル名の配列を1つずつ処理する
foreach($fileArray as $filepath) {
  // basename()でファイル名のみ取得する。
  $file_name = basename($filepath);
}

これでファイル名を取得できるはずなのですが、
ファイル名が全て日本語文字の場合は全て消えて、一部日本語文字の場合は先頭の日本語文字が消えてしまいます。
たとえば
「ファイルsample名前.pdf」→「sample名前.pdf」
「ファイル名.pdf」→「.pdf」
というようになってしまう。
(ちなみにPHPのバージョンは7.1(CGI版)/サーバはheteml)

原因はロケールの設定に依存するため

php.iniでmbstring関連の設定をいじってみたのですが全くの無意味でした。
そして見つけたのが下記の記事
fgetcsv関数を文字化け対応 setlocaleの文字コード指定

fgetcsv関数に関しての記事でしたが、症状は同じ…
ということでさっきのプログラムの頭にロケールの設定を足してみました。
(さっきのコメントは邪魔なので消しました)

sample.php
// ロケールの設定
setlocale(LC_ALL, 'ja_JP.UTF-8');

$fileArray = glob("./file/*");

foreach($fileArray as $filepath) {
  $file_name = basename($filepath);
}

結果は…

わーい!取得できた!よかった!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel 7 ビューファイルで指定したアドレスにメールを送信する

目的

  • ビューファイルからのトリガーでメールを送信する処理を追加する際、非常に詰まったため実施方法をまとめる

実施環境

  • ハードウェア環境(下記の二つの環境で確認)
項目 情報
OS macOS Catalina(10.15.3)
ハードウェア MacBook Pro (16-inch ,2019)
プロセッサ 2.6 GHz 6コアIntel Core i7
メモリ 16 GB 2667 MHz DDR4
グラフィックス AMD Radeon Pro 5300M 4 GB Intel UHD Graphics 630 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.3 Homwbrewを用いて導入
Laravel バージョン 7.0.8 commposerを用いて導入
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いて導入

前提情報

  • ローカル開発環境を用いたメール送信処理の追加方法をまとめる。
  • メール送信サーバはGmailを使用する。
  • 下記の方法を実施して当該アプリからテストメールを送信できる状態にしていること。

概要

  1. 入力フォームの作成
    1. ルーティングファイルの編集
    2. コントローラファイルの作成と編集
    3. ビューフィアルの作成と編集
  2. メール送信の準備
    1. クラス作成
    2. 送信内容の作成
  3. 確認

詳細

  1. 入力フォームの作成

    1. ルーティングファイルの編集

      1. Laravelアプリ名ディレクトリで下記コマンドを実行してルーティングファイルを作成する。

        $ vi routes/web.php
        
      2. 開いたルーティングファイルを下記のルーティング情報を追記する。

        アプリ名ディレクトリ/routes/web.php
        Route::get('/input', 'TestMailController@input');
        Route::post('/send', 'TestMailController@send');
        
      3. 保存して閉じる。

    2. コントローラファイルの作成と編集

      1. アプリ名ディレクトリで下記コマンドを実行してメール送信用のコントローラを作成する。

        $ php artisan make:controller TestMailController
        
      2. アプリ名ディレクトリで下記コマンドを実行して先に作成したコントローラファイルを開く。

        $ vi app/Http/Controllers/TestMailController.php
        
      3. 開いたコントローラファイルを下記の様に修正する。

        アプリ名ディレクトリ/app/Http/Controllers/TestMailController.php
        <?php
        
        namespace App\Http\Controllers;
        
        use Illuminate\Http\Request;
        //下記を追記する
        use Illuminate\Support\Facades\Mail;
        use App\Mail\TestSendMail;
        //上記までを追記する
        
        class TestMailController extends Controller
        {
            //下記を追記する
            public function input()
            {
                return view('test_mails.input');
            }
        
            public function send(Request $request)
            {
                $contact = $request->all();
                Mail::to($contact['email'])->send(new TestSnedMail());
                return redirect('/input');
            }
            //上記までを追記する
        }
        
      4. 保存して閉じる

    3. ビューフィアルの作成と編集

      1. アプリ名ディレクトリで下記コマンドを実行してビューファイルを格納するディレクトリを作成する。

        $ mkdir resources/views/test_mails
        
      2. アプリ名ディレクトリで下記コマンドを実行してビューファイルを開く。

        $ vi resources/views/test_mails/input.blade.php
        
      3. 開いたビューファイルを下記の様に修正する。

        アプリ名ディレクトリ/resources/views/test_mails/input.blade.php
        <form action="/send" method="POST">
            @csrf
            <input type="text" name="email">
            <input type="submit">
        </form>
        
    4. 確認

      1. アプリ名ディレクトリで下記コマンドを実行してローカルサーバを起動する。

        $ php artisan serve
        
      2. 下記にアクセスする。

      3. 下記の様に入力欄と送信ボタンが表示されている事を確認する。

        スクリーンショット 2020-05-18 23.22.36.png

  2. メール送信の準備

    1. アプリ名ディレクトリで下記コマンドを実行してメール送信専用のクラスが記載されたファイルを作成する。

      $ php artisan make:mail TestSendMail
      
    2. アプリ名ディレクトリで下記コマンドを実行して先に作成したメール送信専用のクラスファイルを開く

      $ vi app/Mail/TestSendMail.php
      
    3. 開いたクラスファイルを下記の様に修正する。

      アプリ名ディレクトリ/app/Mail/TestSendMail.php
      <?php
      
      namespace App\Mail;
      
      use Illuminate\Bus\Queueable;
      use Illuminate\Contracts\Queue\ShouldQueue;
      use Illuminate\Mail\Mailable;
      use Illuminate\Queue\SerializesModels;
      
      class TestSendMail extends Mailable
      {
          use Queueable, SerializesModels;
      
          /**
           * Create a new message instance.
           *
           * @return void
           */
          public function __construct()
          {
          }
      
          /**
           * Build the message.
           *
           * @return $this
           */
          public function build()
          {
              //下記を追記
              return $this
                  //メールの件名
                  ->subject('Test Mail')
                  //メールとして表示したいビューファイル
                  ->view('mails.test');
          }
      }
      
    4. アプリ名ディレクトリで下記コマンドを実行してメールとして表示したいビューファイルを格納するディレクトリを作成する。

      $ mkdir resources/views/mails
      
    5. アプリ名で下記コマンドを実行してメールとして表示したいビューファイル作成する。

      $ vi resources/views/mails/test.blade.php
      
    6. 開いたメールとして表示したいビューファイルを下記の様に編集する。

      アプリ名ディレクトリ/resources/views/mails/test.blade.php
      これはテストメールです
      
  3. 確認

    1. アプリ名ディレクトリで下記コマンドを実行してローカルサーバを起動する。

      $ php artisan serve
      
    2. 下記にアクセスする。

    3. 入力欄にテストメールを送信したいアドレスを入力する(正常に実装できているとメールが送信されるので注意!!!)

    4. 入力したアドレスのメール受信ボックスを確認する。

    5. 下記の様にメールが受信できていれば実装完了である。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WEBデータベース Exmentにブラウザでテーブルを作って入力してみる

Exmentとは

オープンソースのWEBデータベース
公式サイト:https://exment.net/

WEBデータベースとは

データベースをブラウザで見たり入力したりすることができ、テーブルの定義もブラウザでできる。ノンプログラミングでデータベースを開発・運用できることが特徴です。
レンタルサーバーなどに設置すれば、テレワークで重宝するかもしれません。

やってみたこと

今回は公式のデモサイトを使って、実際にブラウザからデータベースのテーブルを作ってデータを入力するところまでを紹介します。

テーブル作成

①以下のURLを開く

基本的なデモ環境です。
管理者権限を付与しております。
URL: https://demo-jp.exment.net/admin

01.PNG

②以下を入力して「ログイン」をクリック

ログインID : admin
パスワード: adminadmin

02.PNG

③左側の「管理者設定」をクリック

03.PNG

④「カスタムテーブル」をクリック

04.png

⑤「新規」をクリック

05.png

⑥テーブル名を入れる

 「テーブル名」に英数字でテーブル名(例 EMPLOYEE_LIST)
「テーブル表示名」に日本語でテーブル表示名(例 社員リスト)を入れる
06.PNG

07.PNG

⑦「権限設定」と「メニューに追加する」をONにする

08.PNG
09.PNG

⑧「送信」をクリック

10.png

⑨「カスタムテーブル」をクリックし、追加したテーブル「EMPLOYEE_LIST」をクリック

11.png

⑩「テーブル詳細設定」をクリック

12.png

⑪「カスタム列設定」をクリック

13.png

⑫「新規」をクリック

14.png

⑬「名前」を追加

 「列名(英数字)」に「NAME」、「列表示名」に「名前」を入れ
 「列種類」を「一行テキスト」にする。
 「必須」を「YES」、「ユニーク」を「YES」にし、「送信」を押す
15.PNG

⑭「性別」を追加

 「新規」をクリック。「列名(英数字)」に「GENDER」、「列表示名」に「性別」を入れ
 「列種類」を「一行テキスト」にし「送信」を押す。
16.PNG

⑮「社員番号」を追加

 「新規」をクリック。「列名(英数字)」に「EMPLOYEE_NO」、「列表示名」に「社員番号」を入れ
 「列種類」を「整数」にし「送信」を押す。
17.PNG

18.PNG

⑯一度、「ログアウト」「ログイン」する(※ここではまった)

⑰左のメニューに「社員リスト」が追加されるので、クリックする。

19.png

⑱「新規」をクリック

20.PNG

⑲「名前」「性別」「社員番号」を入力し「送信」を押す

21.PNG
22.PNG

⑳データが保存される。データの行をクリックすると、データを確認できる。

23.PNG

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel7をGCP Google App EngineへデプロイしてDBマイグレーションしてプロモートする

やりたいこと

  • デプロイとDBマイグレーションとプロモート(サイト公開)をコマンドで実行したい

ソース

composer.json

  • composer gcloud-deploy コマンドを登録する
  • chmod 744 gcloud-deploy.bash で実行権限を付与しておく
    "scripts": {
        "gcloud-deploy": [
            "./gcloud-deploy.bash"
        ],

gcloud-deploy.bash

  • jsonのパースにjqを使うのでインストールしておくこと
#!/bin/bash
#set -eu

# proxy実行しているかチェックする
proxyCommand="cloud_sql_proxy -instances=foo-bar-apps:asia-northeast1:foo-bar-mysql=tcp:3306"
ps auxww|grep "$proxyCommand"|grep -v grep
if [ "$?" = "1" ];then
    echo "You must run proxy to do db migrate."
    echo ""
    echo $proxyCommand
    echo ""
    exit 1
fi

npm run prod

# DBマイグレーションはlocalで実行しproxy経由でproductionに接続されるので
# 設定ファイルを.env.productionからコピーする
# 現在使用中の.envはバックアップしておく
rand=$RANDOM && cp .env .env.backup.$rand && cp .env.production .env
message=`php artisan migrate --pretend`
cp .env.backup.$rand .env

# DBマイグレーションが必要かチェックする
if [ "$message" = "Nothing to migrate." ]; then
    # DBマイグレーションが不要なら通常のデプロイ
    echo "Youd don't need to do db migrate."
    echo "Laravel artisan said: $message"
    gcloud app deploy --quiet --project=foo-bar --verbosity=info
else
    # DBマイグレーションが必要ならデプロイオプションで--no-promote(公開しない)
    echo "Youd need to do db migrate."
    echo "Laravel artisan said: $message"
    gcloud app deploy --quiet --project=foo-bar --verbosity=info --no-promote --format=json | tee gcloud-version.json

    # DBマイグレーションする
    rand=$RANDOM && cp .env .env.backup.$rand && cp .env.production .env
    php artisan migrate --force
    cp .env.backup.$rand .env

    # --promote(公開する)
    appVersion=`jq -r '.versions[0].id' gcloud-version.json`
    gcloud app deploy --promote --quiet --version=$appVersion
fi

.gcloudignore

gcloud-version.json
.env.backup.*

ほか

  • コマンドひとつで実行できるようにはなったが課題はある
    • .env.production.env.localの管理したくない
    • というか、現在使用中の.envを上書きされたくない...
  • マイグレーションの必要がないなら通常のgcloud app deployを実行したい
    • migrate --pretendを判定して分岐してみた
  • デバッグ用にdeploy.logとか出力するとsha mismatchエラーでbuildがコケた
    • .gcloudignoreに追加すると解決したような気がする
Step #0 - "fetcher": Failed to fetch gs://staging.foo-bar-hoge.appspot.com/xxxx, will no longer retry: fetching "gs://staging.foo-bar-hoge.appspot.com/xxxx" with timeout 1h0m0s to temp file "/workspace/.download/xxxx": deploy.log SHA mismatch, got "xxxx", want "xxxx"

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プチ・クラウドストレージ作ってみた

初めまして

60歳を間近にウェブデザイナーを目指して独学で勉強しているお婆ちゃんです。

去年の暮れからphpを勉強して、初めてシステムらしきものを作ってみました。

やりたいこと
プチ・クラウドストレージ
・ファイルをどこからでもアップ、保管してダウンロードもできる。
・セキュリティも兼ねてIDとパスワードでログイン形式にする。

まずはパワーポイントでサイトの系図を設計しました。
Excel、Word、パワポは商工会議所で習いたてホヤホヤです。
それぞれ基礎編までクリアして1月半くらいかかりました。
全部で5万円くらいはかかったかな。

siteroot.jpg

次は手順を考えてイメージを具体化。
これは無料版のAdobeのXDを使ってみました。

操作も簡単で、感覚的に作れちゃうので便利です。
ページ自体はシンプルにしたかったのでフォントだけで作りました。
コードを書くのもAdobeの無料Brackets。Adobeドップリ。


ログインページ

AdobeXD イメージ図
Top.jpg
パスワードとIDはここで決めてます。
空文字NGの条件も設定。

login.php
//XSS
function html_escape($word){
    return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
}
$logid = '';
$pass = '';
$messege = '';

if($_SERVER['REQUEST_METHOD'] === 'POST'){
    //isset入れると空文字条件が効かない
    $logid = $_POST['logid'];
    $pass = $_POST['pass'];
    $logid = html_escape($logid);
    $pass = html_escape($pass);

    //IDとパスワード設定
    if($logid === 'keserasera' && $pass === 'keserasera'){
        session_start();
        $_SESSION['login'] = 1;
        //ファイル一覧へリロード
        header('Location: file_list.php');
        exit();
    } elseif ($logid === '' || $pass === ''){
        $messege = '<p class="notice"><i class="fas fa-info-circle"></i>IDとパスワードを空文字にしないで入力してください</p>';
    } else {
        $messege = '<p class="notice"><i class="fas fa-info-circle"></i>IDかパスワード、もしくは両方違います</p>';
    }
}

?>
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>ファイル預かり処・マイ保管庫</title>
   <!-- headerインクルード -->
    <?php require_once(dirname(__FILE__).'/header.php'); ?>
<div id="wrapper">
    <header>
       <div id="titleVar">
          <h1><i class="fas fa-circle"></i>ファイル預かり処 マイ保管庫</h1>
          <p class="tx14">ログインページ</p>
       </div>       
       <div id="topvew">My keep folder</div>             
    </header>
    <main id="topMain">
      <form action="" method="post" class="clearfix">
         <label>ログインID</label>
         <input type="text" name="logid">
         <label>パスワード</label>
         <input type="password" name="pass">
         <p id="logBtn"><input type="submit" value="ログイン"></p>
         <?php echo $messege; ?>
      </form>      
    </main>
    <!-- footerインクルード -->
    <?php require_once(dirname(__FILE__).'/footer.php'); ?>

ファイルリスト一覧ページ

AdobeXD イメージ図
file_list.jpg

アップロードとダウンロード、削除ファイルの3つのformがあります。

  • アップロードは同一ページで処理
  • ダウンロードはチェックページに飛ばしてリロード処理
  • 削除ファイルは確認ページを別に作ってpostデータを渡す

ちなみにダウンロードと削除ファイル一覧リストはタブ切替。
フォルダーの中身は一緒でアップすると自動でリストが増えて、削除すると減っていきます。

file_list.php
//ログインしていないとアクセスさせない
session_start();
session_regenerate_id(true);
if(isset($_SESSION['login']) === false){
    header('Location: un_login.php');
    exit();
}

function html_escape($word){
        return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
    }
$up_file = '';
$messege = '';
$select_file = '<p id="take">ファイルを選択して下さい</p>';
$restore = '';
$up_before = 'upBefore';//非表示css
$filename = '';

if($_SERVER['REQUEST_METHOD'] === 'POST'){
    $up_file = $_FILES['file_up'];
    $up_file['name'] = html_escape($up_file['name']);
    $up_file['name'] = strtolower($up_file['name']);//英小文字に変換  
    //var_dump($up_file['name']);

    $extension = Pathinfo($up_file['name'],PATHINFO_EXTENSION);//.以降の拡張子を重複しないよう整形
    $filename = Pathinfo($up_file['name'],PATHINFO_FILENAME);
    $filename = str_replace('.', '', $filename);//ファイル名に.があったら除去

    //ファイルがNGの場合条件処理
    if($up_file['size'] > 30000000 ){
        $messege = '<p id="caution" class="notice"><i class="fas fa-info-circle"></i>ファイルサイズが容量を超えています</p>';
        $up_before = 'upBefore';
        $restore = '<a id="restore" href="file_list.php">こちらからUPし直してください</a>';
        $select_file = '';
        //phpよりエラー表示を出すときファイル選択ボタンを出さないで再読み込みさせる為、空文字設定に
    } else {
        $messege = '';
        $up_before = 'upAfter';
        //fileをアップする関数
        move_uploaded_file($up_file['tmp_name'], './up_file/'.$filename.'.'.$extension);
    }
 }

   //upフォルダの中身
    $dw_items =  glob('./up_file/*');//DL用、同じでも変数変えないとエラーになる
    $del_items = glob('./up_file/*');//削除用、grobは配列形式でファイルパスを取得
?>
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>ファイル一覧 | ファイル預かり処・マイ保管庫</title>  
<!-- headerインクルード -->
<?php require_once(dirname(__FILE__).'/header.php'); ?>
<div id="wrapper">
    <header>
       <div id="titleVar">
          <h1><i class="fas fa-circle"></i>ファイル預かり処 マイ保管庫</h1>
           <p id="logout"><a href="logout.php">ログアウト</a></p>
       </div>       
       <div id="topvew">My keep folder</div>             
    </header>
    <main id="main">
        <h2 class="pageTitle">ファイルアップロード</h2>
     <!-- ファイルup -->
        <form id="upBox" action="" method="post" enctype="multipart/form-data">
              <p class="notice">※ファイルsizeは1つにつき30MBまで、名前は英小文字で</p>
              <!-- UPし直し表示 -->
              <?php echo $restore; ?>
              <p id="<?php echo $up_before; ?>">ファイル「<?php echo $filename; ?>」はアップされました。続けてUPできます</p>
              <label for="up">
                  <!-- ファイルを選択ボタン -->
                  <?php echo $select_file; ?>
              </label>
              <input id="up" type="file" name="file_up">
              <p id="send"><input type="submit" value=""></p>
              <!-- エラーメッセージ -->
              <?php echo $messege; ?>
        </form>

      <div id="fileBox">
          <p id="fileCount">ファイル数<?php echo count($dw_items); ?>項目</p>         
          <h3 class="selectFile" id="dowTab">DL用ファイル一覧</h3>  
          <form id="dowList" action="download_file.php" method="post">
             <!-- 飛び先でLocation リダイレクト処理-->
              <ul>
                 <?php if(count($dw_items) === 0): ?>
                    <li>まだファイルはありません</li>
                    <?php else: ?>
                      <?php foreach($dw_items as $items): $dw_name = Pathinfo($items,PATHINFO_BASENAME); ?>                   
                     <li><input type="radio" name="dowl" value="<?php echo $dw_name; ?>"><?php echo $dw_name; ?></li>                     
                      <?php endforeach; ?>
                  <?php endif; ?>
              </ul>
              <p class="notice">※左のラジオボタンにチェックしてダウンロードボタンをクリックしてください</p>
              <p class="pibtn"><input type="submit" value="Download"></p>
          </form>

          <h3 class="selectFile" id="delTab">削除用ファイル一覧</h3>
          <form id="delList" action="delete_confilm.php" method="post">
              <ul>
                 <?php if(count($dw_items) === 0): ?>
                    <li>まだファイルはありません</li>
                    <?php else: ?>
                      <?php foreach($del_items as $items): $del_name = Pathinfo($items,PATHINFO_BASENAME); ?>
                         <li><input type="checkbox" name="del[]" value="<?php echo $del_name; ?>"><?php echo $del_name; ?></li>  
                      <?php endforeach; ?>
                  <?php endif; ?>
              </ul>
              <p class="notice">※左のチェックボックスを選択(複数可)して確認ボタンをクリックしてください</p>
              <p class="vibtn"><input type="submit" value="削除確認"></p>
          </form>
      </div><!-- //id="fileBox"-->

    </main>  
<!-- footerインクルード -->
<?php require_once(dirname(__FILE__).'/footer.php'); ?>



input type="file"は特殊で、デフォルトのボタンを使うのはビジュアル面で抵抗があったのでカスタマイズしました。

ファイルが選択されたらボタンがファイル名に変わって、エラーだったら上にメッセージ表示。
OKだったらtype="file"ブロックは非表示になって、type="submit"にすり替え。
見た目は一緒のボタンです。

ファイルアップが成功したらボタンの上にファイル名が表示して下のリストに追加といった仕様。

ただ、ファイル選択時はsubmitボタンは押されてないのでphpでの処理が難しい。
そこでjsのchangeイベントを活用。
ここは頭がこんがらがりました。phpとjsとcssのトリプル連携。

submit_btn.js
jQuery('#up').on('change',function(){
      const upfile = jQuery('#up').get(0).files;
      console.log(upfile);
      //多次元配列になるのかな? upfile[[name: xxx,size: xxx]]
      const fileName = upfile[0].name;            
      console.log(fileName);
       //file選択をsubmitにすり替え
      jQuery('#take').css('display','none');
      jQuery('#send').css('display','block');
      jQuery('#send input').val(fileName);
   });


ダウンロードチェックページ

ダウンロードのコードはどうしても分からなかったのでググって動いたものをコピペさせてもらいました。
それまではダウンロードできても開けられなかったり不具合続出。

ここのコードが一番難しい。
今の段階では理解できなかったのですが、そのうち自分でも組めるようになりたいです。

参考サイトはこちら

download_file.php
//ダウンロードチェック
session_start();
session_regenerate_id(true);
if(isset($_SESSION['login']) === false){
    header('Location: un_login.php');
    exit();
}

function html_escape($word){
        return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
    }
$dowload_file = $_POST['dowl'];
$dowload_file = html_escape($dowload_file);

//var_dump($dowload_file);

//ここのコード分からなかったのでネットからググって拾ってきた
    function download($pPath, $pMimeType = null){
    //-- ファイルが読めない時はエラー(もっときちんと書いた方が良いが今回は割愛)
    if (!is_readable($pPath)) { die($pPath); }

    //-- Content-Typeとして送信するMIMEタイプ(第2引数を渡さない場合は自動判定) ※詳細は後述
    $mimeType = (isset($pMimeType)) ? $pMimeType
                                    : (new finfo(FILEINFO_MIME_TYPE))->file($pPath);

    //-- 適切なMIMEタイプが得られない時は、未知のファイルを示すapplication/octet-streamとする
    if (!preg_match('/\A\S+?\/\S+/', $mimeType)) {
        $mimeType = 'application/octet-stream';
    }

    //-- Content-Type
    header('Content-Type: ' . $mimeType);

    //-- ウェブブラウザが独自にMIMEタイプを判断する処理を抑止する
    header('X-Content-Type-Options: nosniff');

    //-- ダウンロードファイルのサイズ
    header('Content-Length: ' . filesize($pPath));

    //-- ダウンロード時のファイル名
    header('Content-Disposition: attachment; filename="' . basename($pPath) . '"');

    //-- keep-aliveを無効にする
    header('Connection: close');

    //-- readfile()の前に出力バッファリングを無効化する ※詳細は後述
    while (ob_get_level()) { ob_end_clean(); }

    //-- 出力
    readfile($pPath);

    //-- 最後に終了させるのを忘れない
    exit;
}

//選択されたファイルがあったらダウンロード、なかったらそのままリダイレクト
//選択されないままダウンロード処理されちゃうと変なファイルがDLされる
if(isset($_POST['dowl'])){
    download('./up_file/'.$dowload_file);
    header('Location: file_list.php');   
} else {
   header('Location: file_list.php');
}


削除確認、完了ページ

AdobeXD イメージ図
check.jpg

削除だけは誤って消してしまって後悔しないように、確認してからの導線にしました。
完了ページのレイアウトもほぼ一緒です。

delete_done.php
//ログインしていないとアクセスさせない
session_start();
session_regenerate_id(true);
if(isset($_SESSION['login']) === false){
    header('Location: un_login.php');
    exit();
}

function html_escape($word){
    return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
}

$delete_file = '';

//POSTで渡されたファイルを削除
if(isset($_POST['check'])){
    for($i = 0; $i < count($_POST['check']); $i++){           
        unlink('./up_file/'.html_escape($_POST['check'][$i]));
        //削除ファイルli書出し
        $delete_file .= '<li><i class="far fa-file"></i>'.html_escape($_POST['check'][$i]).'</li>';
    }    
}

?>
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>ファイル削除完了 | ファイル預かり処・マイ保管庫</title>
<!-- headerインクルード -->
<?php require_once(dirname(__FILE__).'/header.php'); ?>
<div id="wrapper">
    <header>
       <div id="titleVar">
          <h1><i class="fas fa-circle"></i>ファイル預かり処 マイ保管庫</h1>
           <p id="logout"><a href="logout.php">ログアウト</a></p>
       </div>       
       <div id="topvew">My keep folder</div>             
    </header>

    <main id="confiBox">
        <h2 class="pageTitle">ファイル削除完了</h2>
        <p class="confiText">以下のファイルを削除しました</p>
        <ul>
            <?php echo $delete_file; ?>         
        </ul>
        <p id="toListpage"><a href="file_list.php">ファイル一覧ページへ</a></p>
    </main>

<!-- footerインクルード -->
<?php require_once(dirname(__FILE__).'/footer.php'); ?>

ログアウトページ

ログアウトページは、お約束のセッション破棄だけなのでコードは省きます。
レイアウトはログインページと同じにしました。


ひとまず完成

あとは実際のサーバに上げて動作確認。

動いた時は大感動。
あれ?
でもアップできないファイルがある。

色々試して、どうやら日本語名のファイルはアップできないみたい。

XAMPP開発時では日本語のファイル名でも大丈夫だったんですけどね。
そういえば日本語とサーバの相性は良くないと昔から言われてました。

とりあえず注意書きに日本語NGと追加して応急対処。
preg_matchで条件設定した方がいいのかなと思ったりしています。

ひとまず、これで完成として使い込んでみようと思ってます。
制作期間は約1週間、畑仕事と家事の合間にコツコツと作業

お婆ちゃんのweb制作奮闘記でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む