PHP » MyEgo.cz - Radek Hulán webzine

MyEgo.cz

home foto blogy mywindows.cz kontakt

Jak funguje mkdir / chmod v PHP

Řešil jsem dnes problém na ŠTĚRBA-KOLA.cz, kdy klient mohl nahrát soubory na web pomocí WYSIWYG editoru / PHP, nicméně dále je nemohl smazat přes FTP. Nechápal jsem proč, adresáře se vytvářejí pomocí mkdir($dir,0777), na soubory se nastavuje chmod($fp,0777), a žádná jiná prezentace s tím neměla dosud problém.

Vyřešilo se to až ve spolupráci s webhostingem (tojeono.cz). Problém je, že mkdir 0777 ve skutečnosti nedělá v PHP mkdir 0777. Bere totiž do úvahy i systémovou masku, která je typicky nastavená pro *nixové uživatele na 0022, takže udělá pouhé 0755. A v php.ini ani v httpd.conf se to bohužel nedá změnit. Pokud tuto triviální informaci víte, vše je jasné, já jsem ji do dnešního dne nevěděl. Je tedy nutné před mkdir() použít ještě umask(0000), tedy vynulovat masku, a vše bude fungovat dle očekávání.

Druhou možností je samozřejmě nastavit defaultní umask v *nixovém systému na 0000. Poté i PHP bude fungovat tak, jak očekáváte, a nebude problém v syncu práv mezi PHP a FTP. Je to nakonec nejlepší řešení, protože spousta skriptů umask nepoužívá, a dopisovat to do skriptů je poněkud méně pohodlné. Navíc umask funguje jen pro nejbližší další příkaz, poté se opět resetuje do původního stavu, takže se nedá vložit jen do nějakého config.php.

Bezvadné by bylo, kdyby PHP podporovalo v php.ini cosi jako set_default_umask, pár requestů na to již (pár let) je.

Jak na download PDF souboru v PHP?

Když jsem včera řešil generování PDF z HTML a následný download tohoto souboru, setkal jsem se s řadou problémů. Není to v praxi až tak triviální věc, jak to vypadá. Některé zasílané header() hlavičky nefungují v MSIE, některé v Opeře, a jiné zase ve Firefoxu. Dospěl jsem nakonec k řešení, které funguje, dle testů, pod MSIE 6.0, Firefoxem 1.5 i Operou 9, a nabídne uživateli stažení vytvořeného PDF souboru.

Takto vypadá skutečně funkční download vyvořeného PDF v PHP:

function download_file($pdfdata, filename){
 if (ereg('Opera(/| )([0-9].[0-9]{1,2})', $_SERVER['HTTP_USER_AGENT']))
  $UserBrowser = "Opera";
 elseif (ereg('MSIE ([0-9].[0-9]{1,2})', $_SERVER['HTTP_USER_AGENT']))
  $UserBrowser = "IE";
 else
  $UserBrowser = '';
 $mime_type = ($UserBrowser == 'IE' || $UserBrowser == 'Opera') ? 
  'application/octetstream' : 'application/octet-stream';
 header('Content-Type: ' . $mime_type);
 header('Content-Disposition: attachment; filename="'.$filename.'"');
 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
 header('Accept-Ranges: bytes');
 header("Cache-control: private");
 header('Pragma: private');				
 header("Content-Length: ".strlen($pdfdata));
 echo $pdfdata;
}

Otestovat si jej můžete přímo pod tímto článkem kliknutím na [export do PDF].

Základy SQL, část 1.

V této sérii článků budu popisovat poněkud opomíjenou věc - SQL a PL/SQL syntax pro současné databáze, a to konkrétně pro opensource produkt MySQL 5.x (tedy včetně VIEWS, TRIGGERS, atd.). Řada lidí se nějak naučí programovat v PHP, ale kvalitní použití SQL pro ně zůstává záhadou. A to je velice špatně.

Osobně veškeré rozsáhlé aplikace píšu zásadně nad databází (typicky Oracle 8 až 10g). Proč? Řada činností se dá rozdělit na události, a tyto události, pokud je napíšete jako triggery a uložené procedury, není nutné explicitně volat. Ať už přistupujete do databáze z webové aplikace, z Delphi či z .NET aplikace, máte záruku, že daná akce se vždy provede. Navíc je zpracování dat nad databází mnohem efektivnější a rychlejší než tahat všechna data na klienta, tam to aplikačně zpracovat, a poté data posílat zpět k uložení na server.

Ubohá znalost SQL je dána i zcela nechutným rozšířením jedné extrémně špatné databáze, tedy MySQL 3.x a 4.x. Mírně lepší je až MySQL 5.x, nicméně, její nasazení na hostingu není zatím moc reálné, řada českých webhostingů se sotva dopracovala k MySQL 4.1.

Tolik k úvodu, a následuje první, pochopitelně nejsnazší, část tohoto seriálu.

SELECT a LEFT/RIGHT/INNER JOIN

Představte si, že máte nějaké fórum, ve kterém ja tabulka příspěvků (TOPICS) a tabulka práv uživatelů (PERMS). Pokud administrátor nic nenastaví, je tabulka PERMS prázdná a platí defaultní práva systému, pokud chce defaultní práva změnit, zapíše přes administrační rozhraní práva pro jednotlivé skupiny uživatelů do tabulky PERMS.

Známe Vaše heslo!

Mám hrůzu ze služeb a programátorů, kteří ukládají hesla klientů jako plain-text. Tedy v lidsky čitelné podobě. Když jsem včera žádal o obnovu hesla na Cybex.cz, kde jsem se kdysi dávno registroval, přišlo mi toto:

Známe tvoje heslo

Jinak řečeno, jakýkoliv administrátor podobného webu či hacker získající přístup do databáze zná Vaše skutečné heslo. A to není moc příjemné. Místo například sha1 hashe hesla v podobě 59af7f2e fa5fb2d7 c2081a87 04e0a003 43e5ff6e je uloženo přímo heslo samotné, tedy znamevaseheslo. Nyní si představte, že stejný účet a heslo používáte do dalších deseti obchodů. Začínáte se děsit?

Pokud klient zapomene heslo, existuje jediná relativně bezpečná cesta, jak mu sdělit jeho heslo, a to vygenerovat heslo zcela nové, jeho sha1 či md5 hash uložit do databáze, a nové heslo poslat na jeho registrační email.

Nicméně, protože nemůžete nikdy vědět jak „prasácky“ je naprogramovaná ta která webová aplikace, tak nezbývá než pro každou Vaši registraci používat jiné heslo a minimalizovat tím jeho potencionální zneužití.

PHP neobsahuje, obsahuje, neobsahuje SQLite

Když jsem před půl rokem s radostí přivítal, že PHP 5.0 pro Windows obsahovalo standardně v sobě SQLite extension (a také třeba iconv), tak musím konstatovat, že v PHP 5.1 je zase všechno jinak. Chlapci pracující na PHP mají zjevně rádi náhodné změny, a tak se rozhodli, že PHP 5.1 už zase SQLite obsahovat zakompilované nebude, ale je nutné je explicitně načíst jako extension. A aby to nebylo tak úplně jednoduché, je nutné v php.ini načíst nejenom extension=php_sqlite.dll, ale rovnou takto:

extension=php_pdo.dll
extension=php_sqlite.dll

Chlapcům z PHP děkuji, jen více a více neustálých úžasných změn! Tedy, já tuším, že to je kvůli zahrnutí PDO, ale stejně by to chtělo trošku více konzistence a konzervativního postoje. Není důvod, aby non-PDO php_sqlite.dll potřebovala ke své funkci načtené PDO, taková mysql a mysqli extension to samozřejmě nepotřebuje. Je to bordel. Takto na novější verze nepřejde skoro nikdo, protože kompatibilita neexistuje.

Převod dat z MySQL 4.0 do MySQL 4.1

MySQL 4.0 do 4.1

Upravoval jsem dnes prezentaci Města Český Brod (do redakčního systému byla doplněna například automatická expirace článků či zobrazení počtu článků v dané sekci, což jsou velice podstatné funkce pro funkci Úřední deska) a při té příležitosti jsem chtěl i přesunout data z MySQL 4.0 databáze (mysql) do MySQL 4.1 (mysqli).

Požádal jsem tedy webhosting tojeono.cz o vytvoření nové databáze v MySQL 4.1, a zkopírování datových souborů. Problém ovšem byl, že pouhé zkopírování dat nestačí. Tabulky v MySQL 4.0 jsou po převodu do MySQL 4.1 v latin2 kódování (což je hlavní důvod pro úpravu, celý redakční systém jede v UTF-8), a v MySQL 4.1 jsem potřeboval UTF-8. Výsledkem ovšem byla změť znaků, přestože binárně se o unicode jednalo. Nepomohla ani změna typu tabulky z latin2 na binary a poté z binary na UTF-8. PHPMyAdmin zobrazoval pořád nesmyslné znaky.

Jediná cesta, která fungovala, bylo vytvořit novou databázi v MySQL 4.1 s UTF-8 kódováním, exportovat danou tabulku, starou dropnout, vytvořit ji následně znovu, a následně importovat dříve exportovaná data. Za pomoci PHPMyAdmina práce na 10 minut, a web je poté plně fukční, a řadí hezky česky, nicméně, stejně nechápu, proč běžné postupy jako je nastavení COLLATE a CHARACTER SET nefungují, a tabulku je potřeba fyzicky DROPnout a udělat pomocí CREATE novou.

PHP knihovna pro Captcha a kontaktní formulář

Vzhledem k množícímu se spamu, který přichází přes standardní kontaktní formulář, jsem si napsal cca 1.000 řádkovou knihovnu v PHP, která tomuto jednou provždy zamezí. Ono lze kontaktní formulář obecně zneužít k rozesílání spamu, a to velice snadno, aniž o tom autor původního skriptíku tuší.

Základem knihovny je třída CAPTCHA, a následný potomek CONTACT, které implementují veškeré akce nutné pro Captcha, tedy vygenerování Captcha klíče, jeho autorizaci, a následného poslání emailu.

Vlastní třída je velice dobře dokumentovaná přímo ve zdrojovém kódu, který si můžete stáhnout v souboru captcha.zip (36 KB), podívat se na něj online, a zde je vidět v praxi. Pokud tuto knihovnu někde použijete, budu rád, když mi pošlete email.

Vlastní použití třídy CONTACT je velice snadné, nejlépe je ilustruje následující příklad:

<form method="post" action="index.php#captcha" id="captcha">
<?php
 require('captcha.php');
 $email = new CONTACT('your@email.cz');
 $email->DefaultMessage = '[zprava]';
 $email->doFormExtras();
 $email->doMail();
?>
 <fieldset><legend>Kontakt</legend>
  <p><label for='idname'>Jmeno:</label> 
   <input type="text" name="name" id='idname' tabindex="1" size="40" /></p>
  <p><label for='idemail'>E-mail:</label> 
   <input type="text" name="email" id='idemail' tabindex="2" size="40" /></p>
  <p><label for='idtelephone'>Telefon:</label> 
   <input type="text" id='idtelephone' name="telephone" tabindex="3" size="40" /></p>
  <p><textarea name="message" cols="50" rows="10" tabindex="4"><?php if (isset($_POST['message'])) echo htmlspecialchars($_POST['message']); else echo $email->DefaultMessage; ?></textarea></p>
  <p><input type="submit" value="Poslat!" tabindex="5" /></p>
 </fieldset>
</form>

Uvedený skript vyžaduje pro svoji práci PHP verze 5.X, včetně podpory pro SQLite. Skripty pro PHP 4.x již nepíšu.

Vlastní CAPTCHA třídu samozřejmě můžete použít i pro jiné účely než kontaktní formulář, třída CONTACT je jen jedním z mnoha možných příkladů její implementace.

Rekurze je sexy!

Rekurze je rozhodně velice sexy, protože se s minimem kódu dají dělat mraky operací velice jednoduše :-)

function copyIt($a,$b){
 $c = scandir($a);
 foreach($c as $d) {
  if ($d != '.' && $d != '..') {
   $e = $b.$d;
   $f = $a.$d;
   if (is_dir($f)) {
     mkdir($e);
     copyIt($f.DIRECTORY_SEPARATOR,$e.DIRECTORY_SEPARATOR);
   } elseif (is_file($f))
    copy($f, $e);
  }
 }
}

Otázka pro PHP začátečníky: víte, co tento kód dělá?

Zend’s PHP 5 Coding Contest - učíme se PHP z chyb ostatních

PHP 07.01.2005

Na Interval.cz vyšel můj článek Zend’s PHP 5 Coding Contest - učíme se PHP z chyb ostatních. Doporučuji jej k přečtení, dívám se v něm na 5 nejhorších aplikací této soutěže, a je to psané docela zajímavým stylem.

Zend’s PHP 5 Coding Contest - více praxe, méně teorie

PHP 24.12.2004

Na Interval.cz vyšel můj článek Zend’s PHP 5 Coding Contest - trochu méně teorie a více praxe, který popisuje hned 6 užitečných aplikací z této soutěže.

Banan.cz