Kirjautuminen

Haku

Tehtävät

Kilpailu

Ohjelmoi tekoäly!
Aikaa on 6.1. saakka.

Koodivinkit: PHP: Tiedostolista kotisivulle

Kirjoittaja: qalle

Kirjoitettu: 06.01.2016 – 17.03.2019

Tagit: käyttöliittymä, ohjelmointitavat, web

PHP-/HTML-/CSS-sivu, joka tulostaa taulukon samassa hakemistossa olevista tiedostoista, alihakemistoista, niiden ko’oista ja muokkausajankohdista. PHP-koodin alussa on musta ja valkoinen lista, jolla voidaan estää tiettyjen tiedostojen ja alihakemistojen näkyminen taulukossa. Listat tukevat PHP:n fnmatch()-funktion mukaisia korvausmerkkejä (mm. "*").

Viimeisin muutos: PHP-aloitustägit <? muotoon <?php

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>File list</title>
<style type="text/css">
    /* taulukko ja kaikki sen solut */
    table#filelist, table#filelist th, table#filelist td {
        border-style:solid;  /* yhtenäiset reunaviivat */
        border-width:1px;  /* yhden pikselin paksuiset reunaviivat */
    }

    /* taulukko */
    table#filelist {
        border-collapse:collapse;  /* yhdistä toisissaan kiinni olevat reuna-
        viivat toisiinsa (näytä vain yhtenä) */
    }

    /* taulukon kuvaus */
    table#filelist caption {
        caption-side:bottom;  /* muun sisällön alapuolella */
        font-family:sans-serif;  /* pääteviivaton fontti */
        padding:.4em;  /* sisällön etäisyys reunoista 0,4 em-yksikköä */
    }

    /* data-elementti taulukon kuvauksessa */
    table#filelist caption data {
        font-weight:bold;  /* lihavoi */
        white-space:nowrap;  /* älä koskaan jaa usealle riville */
    }

    /* kaikki solut */
    table#filelist th, table#filelist td {
		empty-cells:show;  /* näytä tyhjät solut */
        text-align:left;  /* tasaa sisältö vasemmalle */
        vertical-align:middle;  /* tasaa sisältö pystysuunnassa keskelle */
        padding:.4em .8em;  /* sisällön etäisyys reunoista pystysuunnassa 0,4
        em-yksikköä ja vaakasuunnassa 0,8 em-yksikköä */
    }

    /* otsikkosolut */
    table#filelist th {
        font-family:sans-serif;  /* pääteviivaton fontti */
    }

    /* linkit tiedostoihin ja hakemistoihin */
    table#filelist td.name a {
        font-family:monospace;  /* tasalevyinen fontti */
        font-weight:bold;  /* lihavoi */
        text-decoration:none;  /* poista alleviivaus */
    }

    /* koko-sarakkeen solut */
    table#filelist .size {
		text-align:right;  /* tasaa sisältö oikealle */
    }

    /* tiedostojen koot */
    table#filelist td.size {
        white-space:nowrap;  /* älä koskaan jaa usealle riville */
    }
</style>
</head>
<body>

<?php

/*
Seuraava lista määrää, mitkä tiedostot ja alihakemistot näytetään ja mitkä pii-
lotetaan.

Kukin avaimen ja arvon yhdistelmä (avain => arvo) määrittää yhden ehdon:
avain: fnmatch()-funktion mukainen tiedostonimi mahdollisilla jokerimerkeillä;
       poikkeus: kauttaviiva (/) lopussa tarkoittaa, että ehto koskee vain ali-
       hakemistoja; muuten ehto koskee vain tiedostoja
arvo:  True, jos avaimen määrittämät tiedostot/alihakemistot näytetään, tai
       False, jos piilotetaan

Jos sama tiedosto/alihakemisto on määrätty sekä näytettäväksi että piilotetta-
vaksi, sitä ei näytetä.

Jos tiedosto/alihakemisto ei täsmää mihinkään ehtoon, sitä ei näytetä.

Ehtojen järjestyksellä ei ole väliä (paitsi tietysti jos sama avain esiintyy
useasti, jolloin vain sen viimeinen arvo jää voimaan).
*/

const BLACK_AND_WHITE_LIST = [
    "*/" => True,  # hyväksy kaikki hakemistot
    "./" => False,  # paitsi .
    "../" => False,  # ja ..
    "private-*/" => False,  # ja "private-"-alkuiset

    "*.png" => True,  # hyväksy kaikki .png-päätteiset tiedostot
    "salainen.png" => False,  # paitsi salainen.png
    "spede.*" => True,  # hyväksy kaikki "spede."-alkuiset tiedostot
    "??" => True,  # hyväksy kaikki 2-merkkiset tiedostonimet
];

/*
Taulukon sarakkeiden nimet ja kuvaukset.
avain: nimi GET-parametrissa, joka määrää listan järjestyksen
arvo: [
    "class" => CSS-luokan nimi,
    "description" => käyttäjälle näytettävä sarakkeen nimi
]
*/
const COLUMNS = [
    "name" => [
        "class" => "name",
        "description" => "file/subdirectory",
    ],
    "size" => [
        "class" => "size",
        "description" => "size",
    ],
    "modified" => [
        "class" => "last-modified",
        "description" => "last mod&shy;ified",  # &shy; = pehmeä tavuviiva
    ],
];

# tiedostokokojen tuhaterotin (ei tarvitse olla sitova välilyönti; ks. CSS)
const THOUSAND_SEPARATOR = " ";

# päivämäärien ja aikojen muoto; ks. https://www.php.net/manual/en/function.date.php
const TIME_FORMAT = "Y-m-d H:i:s";

function get_order_argument() {
    # lue lajitteluparametri

    # lue arvo
    $order = $_GET["order"];

    # palauta arvo, jos se löytyy hyväksyttyjen listalta, muuten oletusarvo
    if(array_key_exists($order, COLUMNS)) {
        return $order;
    }
    return array_keys(COLUMNS)[0];
}

function accept_name($name) {
    /* Määrittele, näytetäänkö tiedosto- tai hakemistonimi listalla. (True =
    kyllä, False = ei.) */

    if(is_dir($name)) {
        $name .= "/";
    }

    $onWhitelist = False;

    foreach(BLACK_AND_WHITE_LIST as $pattern => $accept) {
        # FNM_PATHNAME estää jokerimerkkejä vastaamasta mahdollista kauttavii-
        # vaa lopussa
        if(fnmatch($pattern, $name, FNM_PATHNAME)) {
            if($accept) {
                # valkoisella listalla; muista se
                $onWhitelist = True;
            } else {
                # mustalla listalla; hylkää heti
                return False;
            }
        }
    }

    # hyväksy jos ja vain jos oli valkoisella muttei mustalla listalla
    return $onWhitelist;
}

function get_and_sort_files($order) {
    # palauta lajiteltu lista näytettävistä tiedostoista ja alihakemistoista

    # hae tiedostonimet
    $names = array_filter(scandir("."), "accept_name");

    # hae tiedostojen muut tiedot (omiin taulukoihinsa, koska array_multisort()
    # vaatii niin)
    $isDirectory = [];
    $sizes = [];
    $lastModified = [];
    foreach($names as $name) {
        $isDirectory[] = is_dir($name);
        $sizes[] = (is_dir($name) ? 0 : filesize($name));
        $lastModified[] = filemtime($name);
    }

    # lajittele tiedostot
    if($order == "size") {
        array_multisort($isDirectory, SORT_DESC, $sizes, $names, $lastModified);
    } elseif($order == "modified") {
        array_multisort($lastModified, SORT_DESC, $names, $isDirectory, $sizes);
    } else {
        array_multisort($names, $isDirectory, $sizes, $lastModified);
    }

    # sijoittele tiedostojen tiedot uuteen taulukkoon
    $files = [];
    for($i = 0; $i < count($names); $i++) {
        $files[$names[$i]] = [
            "isDirectory" => $isDirectory[$i],
            "size" => $sizes[$i],
            "lastModified" => $lastModified[$i],
        ];
    }

    return $files;
}

function format_order_link($currentOrder, $newOrder, $description) {
    /* Muotoile linkki, joka vaihtaa tiedostojen/alihakemistojen järjestystä.
    $currentOrder: valittuna oleva järjestys
    $newOrder: järjestys, johon tämä linkki vaihtaa
    $description: tämän linkin teksti
    */

    if($newOrder == $currentOrder) {
        # ei linkkiä
        return $description;
    }

    if($newOrder == array_keys(COLUMNS)[0]) {
        # oletusjärjestys
        return sprintf('<a href=".">%s</a>', $description);
    }

    return sprintf('<a href="?order=%s">%s</a>', $newOrder, $description);
}

function my_number_format($size) {
    /* Muotoile kokonaisluku. */

    return number_format($size, 0, ".", THOUSAND_SEPARATOR);
}

function format_item_link($name, $isDirectory) {
    /* Muotoile linkki tiedostoon tai alihakemistoon. */

    return sprintf(
        '<a href="%s">%s%s</a>',
        htmlspecialchars($name),
        htmlspecialchars($name, ENT_NOQUOTES),
        ($isDirectory ? "/" : "")
    );
}

function format_item_size($size, $isDirectory) {
    /* Muotoile tiedoston tai alihakemiston koko. */

    if($isDirectory) {
        return "";
    }

    return sprintf("<data value=%d>%s</data>", $size, my_number_format($size));
}

$order = get_order_argument();
$files = get_and_sort_files($order);
$itemCount = count($files);
$totalFileSize = array_sum(array_column($files, "size"));

/* Tulosta taulukko tiedostoista ja alihakemistoista. */

?>
<table id="filelist">
    <caption>items: <data value=<?= $itemCount; ?>><?= my_number_format($itemCount); ?></data>, total file size: <data value=<?= $totalFileSize; ?>><?= my_number_format($totalFileSize); ?></data></caption>

    <thead>
        <tr>
<?php foreach(COLUMNS as $name => $info) { ?>
            <th class="<?= $info["class"]; ?>"><?= format_order_link($order, $name, $info["description"]); ?></th>
<?php } ?>
        </tr>
    </thead>

    <tbody>

<?php foreach($files as $name => $info) { ?>
        <tr>
            <td class="name"><?= format_item_link($name, $info["isDirectory"]); ?></td>
            <td class="size"><?= format_item_size($info["size"], $info["isDirectory"]); ?></td>
            <td class="last-modified"><?= date(TIME_FORMAT, $info["lastModified"]); ?></td>
        </tr>
<?php } ?>
    </tbody>
</table>

</body>
</html>

Kommentit

Metabolix [06.01.2016 14:25:04]

Lainaa #

DOCTYPE HTML 4.01 on aika muinainen. Samoin merkistökoodauksen voi merkitä nykyaikaisemmin meta charset -tagilla.

CSS-koodi saisi olla siistimmin rivitettyä.

kalles kirjoitti:

if(
	!is_dir($file) &&
	IsOnList($file, $fileWhiteList) &&
	!IsOnList($file, $fileBlackList)
	||
	is_dir($file) &&
	IsOnList($file, $dirWhiteList) &&
	!IsOnList($file, $dirBlackList)
) {
	$filesToShow[] = $file;
}

Tämä if-lause on sekä ruma (tarpeetonta IsOnList-toistoa) että is_dir-tarkistuksen osalta hyödytön.

Mielestäni echo ja sprintf eivät sovi kovin hyvin yhteen; jos kuitenkin on tarkoitus tulostaa, miksei saman tien ole pelkkä printf koko tulostukselle?

Miksi tuhaterotin on alussa vakiona mutta desimaalierotin ei?

qalle [06.01.2016 15:10:08]

Lainaa #

Kiitos kommenteista.

Mielestäni is_dir()-tarkistus tarvitaan, koska hakemistonimille on eri white- ja blacklist kuin tiedostonimille.

PHP:n "laiskan evaluoinnin" takia kaikki if()-lauseen tarkistukset suoritetaan vain harvoin.

Desimaalierotinta ei käytetä koskaan, koska tiedostokoot näytetään aina tavuina. (Jos number_format():ille annetaan tuhaterotin, myös desimaalierotin on annettava.)

Siistin kohta CSS:ää, if()-lausetta ja tulostusta. En jaksa tähän hätään ruveta opettelemaan uudempaa HTML-versiota.

Lisäys: Editoitu. Vaihdoin myös muuttujien nimiä selkeämmiksi, koska aiemmin hakemistoja kutsuttiin välillä tiedostoiksi ja välillä ei.

Metabolix [06.01.2016 16:20:18]

Lainaa #

Oho, luinkin is_dir-kohdan väärin, en huomannut, että oli erilliset listat. Toisaalta herää kysymys, tarvitseeko olla erilliset listat. Tarkistuksesta saisi joustavamman tekemällä vain yhden listan, joka käydään läpi alusta loppuun ja josta otetaan ensimmäinen osuma:

$accept = [
	"mustallalistalla.png" => false,
	"*.png" => true,
	"index.php" => true,
	"*.php" => false,
	"*" => false,
];

Tarvittaessa vielä /-merkki hakemistoihin jo tässä vaiheessa, niin voi erottaa ne tarkastuksessa.

Mutta kuinka vain.

Tarvitseeko "." ja ".." tarkistaa erikseen, vai voisivatko ne hoitua blacklistin kautta?

Uudempi HTML:n versio ei välttämättä edellytä kuin seuraavat muutokset:

<!DOCTYPE html>
...
<meta charset="UTF-8">

qalle [05.02.2016 21:24:26]

Lainaa #

Demo toimii taas.

qalle [09.07.2017 23:23:43]

Lainaa #

Päivitetty.

Demo (edellisen viestin linkki ei enää toimi)

decoy [04.10.2018 12:25:45]

Lainaa #

kiitoksia, tässä vähän tavaillu php:tä ja oli mukavaksi avuksi tämä .

ja selkeydestä erityinen kunniamaininta ja kiitos.

hyvää alkanutta syksyä jos tännepäin tulet vastauksia lukemaan. vähän on hiljaísta näillä seinillä meno.

qalle [21.10.2018 00:28:00]

Lainaa #

Kiitos. Uusi linkki demoon

Metabolix [26.02.2019 11:53:50]

Lainaa #

Avaustagi <? ei ole muodissa, koska sen olemassaolo riippuu PHP:n asetuksista. Olisi hyvä vaihtaa täysimittaiseen merkintään <?php. Toki tulostuksessa <?= toimii tästä riippumatta.

qalle [17.03.2019 19:19:47]

Lainaa #

Vaihdettu avaustägit. Demoa ei ole enää, koska ei ole kotisivutilaakaan.

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta