Archiv autora: Jan Kocmánek

Porovnání kurzu dolaru Revolut vs. PayPal

Stručně a jasně. Úkol zněl poslat někam 100 $. Buď jsem mohl nechat konverzi Kč na USD nechat na PayPalu – výchozí možnost nebo změnit a nechat to na kartě.

Výsledek?

PayPal 1 Kč = 0,0412810 USD
Revolut 1 Kč = 0,0430 USD
Čtěte: Za jednu korunu dostanu X USD.

Takže, pokud potřebuji vyměnit 100 USD, tak mě to bude stát:
PayPal 2422,4219,- Kč
Revolut 2325,5814,- Kč

100,- Kč najít na cestě za pár minut…

A pokud si nyní zaregistrujete Revolut kartu, tak získáte navíc 300,- Kč jen tak.

Zaokrouhlování ceny s DPH – po novelizaci 2019

Nevím proč, ale nenašel jsem relevantní informace, jak toto nové zaokrouhlování konkrétně implementovat a tak jsem si zjistil nějaké detaily a sepsal tento kratičký blog.

Konkrétní situace, která mě zajímá:
– cenu počítám z ceny bez DPH
– požaduji platbu hotovostí (je třeba zaokrouhlovat)

Jaká byla situace dříve?
Pokud byla cena bez DPH 150 Kč, tak se DPH spočítalo jako 150 * 1,21 (pro DPH 21%) a tedy cena s DPH byla 181,5 Kč. Pokud jste dále platili hotovostí, tak se cena zaokrouhlila na 182 Kč. A těch 0,5 Kč zůstalo bez zdanění!
Ale úředník si řekl, že to je jasný únik příjmů z daní a co se tedy stalo. Tento zaokrouhlený padesátník, je třeba dále rozdělit na výši základu a daň. Tedy 0,5 Kč si ještě musíme rozdělit na část základu a část k zdanění. To uděláme jako 0,5 * 0,21 = 0,11 Kč a tedy nový základ daně nebude 150 Kč, ale 150 + (0,5 – 0,11) = 150,39 Kč.

Samozřejmě existuje i případ, kdy tímto způsobem stát o daň připravíme. Jestli bude díky této úpravě celkově v plusu či v mínusu, ale říct nedokážu…

Celé!


Upozornění: Autor článku není žádný daňový specialista. Informace výše uvedené podává, tak jak je našel, pochytil a pochopil a také jak jim bezmezně věří.

Zabezpečení WordPress administrace pomocí .htaccess

Jednou mi tak volá hoster, že probíhá cílený útok na tento blog (nechci se nyní zabývat detaily). Nebojím se o sílu svého hesla, ale nadměrně to zatěžuje server.

Řešení je jednoduché!
Do souboru .htacces jsem přidal následující blok:

<IfModule mod_rewrite.c>
	RewriteEngine on
	RewriteCond %{REQUEST_URI} ^(.*)?wp-login\.php(.*)$ [OR]
	RewriteCond %{REQUEST_URI} ^(.*)?wp-admin$
	RewriteCond %{REMOTE_ADDR} !^1\.2\.3\.4$
	RewriteCond %{REMOTE_ADDR} !^4\.3\.2\.1$
	RewriteRule ^(.*)$ - [R=403,L]
</IfModule>

Toto pro IP 1.2.3.4 a 4.3.2.1.

Hotovo!

Jistě jsou i jiná řešení. Například pomocí nějakých pluginů, ale pro mě jako člověka programování znalého toto přijde nejrychlejší… A hlavně! Plugin znamená, že to bude veřejná metoda a bude jen otázka času, než se někomu vyplatí na takový prvek napsat robota, který s ním bude počítat…

Fotoalbum v NETTE (flickr style) – jak obejít databázi

Dva body, které mi vadili na starém albu:

  • složité nahrávání fotek (přes již prastarou aplikaci Olympus Camedia Photoalbum)
  • proprietální databáze Olympus Camedia Photoalbum postavená na MS ACCESS 1997, bez rozumné možnosti rozšíření

A jeden bod, který jsem měl jako požadavek na album nové:

  • minimální závislost na databázi

Když jsem psal tyto řádky netušil jsem, že se databáze budu schopen zbavit úplně (alespoň na straně uživatele). Došlo mi to ve chvíli, kdy jsem nahrával první album a následně jsem se musel přepínat do prostředí databáze a tam zadávat údaje (jméno, cesta, parent_id, …). Čím to nahradit?

INI souborem. Výhoda je, že je to i pro laika pochopitelné. Stačí do přidávané složky nahrát soubor album.ini a v něm mít následující obsah:

[album]
nazev="Vildštejn - Mohyly"
dukaz=1753
datum="5. - 6. 10. 2013"
 
[nezobrazovat]
fotka[]=DSC_9317.JPG
fotka[]=DSC_9318.JPG

Toto nepotřebuje delšího komentáře. V oddíle je zvykem mít název tvořen ve tvaru „1753 – Vildštejn – Mohyly 5. – 6. 10. 2013“ a obsah souboru je pochopitelný i pro laika.

Ještě drobné zabezpečení v .htaccess.

RewriteRule (.*)album.ini$ - [F]

Práce se souborem album.ini:
Načítání (zatím i s databází):

	public function getNameFromPath($path) {
		$row = $this->connection->fetchColumn('SELECT `desc` FROM `I_dir` WHERE `webalize-path`=? LIMIT 1', strtr($path, '/', '\\'));
		if ($row['desc'] == '') {
			$ini_array = @parse_ini_file(ALBUM_PATH . $path . 'album.ini');
			if (!isset($ini_array['nazev'])) return 'Album nemá vyplněno název';
			return iconv('WINDOWS-1250', 'UTF-8', "{$ini_array['dukaz']} - {$ini_array['nazev']} {$ini_array['datum']}");
		} else {
			return $row;
		}
	}

Ukládání:
Bude potřeba celou databázi převést na album.ini soubory a dále ve webovém rozhraní opatřit admin módem, aby šlo z rozhraní označit fotky, které nemají jít vidět směrem ven. Na webu jsem nenašel rozumnou komplexní funkci/třídu pro zápis do tohoto formátu, tak jsem si ji napsal sám.

Funkce dostane cestu k souboru a pole, které uloží do struktury. Tedy nejen že uloží, ale umí do souboru i data přidávat ne jen přepisovat.

	/**
	 *	přidá do existujícího INI souboru hodnoty ve tvaru $arr['klic']['podklic'] = $hodnota
	 *
	 */	 	 	 	
	public function saveIni($array, $path) {
		$ini_array = parse_ini_file(ALBUM_PATH . $path . 'album.ini', true);
 
		$res = array();
		foreach($ini_array as $key => $val) {
		    if(is_array($val)) {
		        $res[] = "[$key]";
		        foreach($val as $skey => $sval) {
		        	if(is_array($sval)) {
		        		foreach($sval as $skey1 => $sval1) {
							$res[] = "{$skey}[] = ".(is_numeric($sval1) ? $sval1 : '"'.$sval1.'"');
						}
					} else {
						$res[] = "$skey = ".(is_numeric($sval) ? $sval : '"'.$sval.'"');
					}
				}
				if (array_key_exists($key, $array)) {
					foreach($array[$key] as $skey => $sval) {
						if(is_array($sval)) {
			        		foreach($sval as $skey1 => $sval1) {
								$res[] = "{$skey}[] = ".(is_numeric($sval1) ? $sval1 : '"'.$sval1.'"');
							}
						} else {
							$res[] = "$skey = ".(is_numeric($sval) ? $sval : '"'.$sval.'"');
						}
					}
					unset($array[$key]);
				}
				$res[] = '';
		    } else {
				$res[] = "$key = ".(is_numeric($val) ? $val : '"'.$val.'"');
				if (array_key_exists($key, $array)) {
					$res[] = "$array[$key] = ".(is_numeric($array[$key]) ? $array[$key] : '"'.$array[$key].'"');
					unset($array[$key]);
				}
			}
		}
		foreach($array as $key => $val) {
			if(is_array($val)) {
		        $res[] = "[$key]";
		        foreach($val as $skey => $sval) {
		        	if(is_array($sval)) {
		        		foreach($sval as $skey1 => $sval1) {
							$res[] = "{$skey}[] = ".(is_numeric($sval1) ? $sval1 : '"'.$sval1.'"');
						}
					} else {
						$res[] = "$skey = ".(is_numeric($sval) ? $sval : '"'.$sval.'"');
					}
				}
				$res[] = '';
		    } else {
				$res[] = "$key = ".(is_numeric($val) ? $val : '"'.$val.'"');
			}
		}
 
		file_put_contents('safe://' . ALBUM_PATH . $path . 'album.ini', implode("\r\n", $res));
	}

Jediné co mi nyní na ni vadí je standardní funkce parse_ini_file(), která nenačte komentáře a tak o ně tímto přijdeme.

Zapomenuté přihlašovací heslo k Windows 7

Tato peripetie mi sebrala 3 hodiny života a kdyby mi to někdo vyprávěl asi bych mu nevěřil.

Prolog – výchozí stav:
Windows mám nastaven tak, aby se mi automaticky přihlašoval bez nutnosti zadat heslo. Pokud vím, tak tam žádné heslo zadané nemám, ale to nakonec nehraje roli…

Akt 1 – pád Windows a nastartování
Trochu jsem koulel očima, když jsem viděl nastartovanou obrazovku a na ní přihlašovací dialog. Samozřejmě žádné z možných (mnou požívaných) hesel nefungovalo. Restart také nepomohl. Koukal tam na mě odkaz „Reset password“ ale po kliknutí to chtělo removable medium, které po připojení zjistilo, že tam není nainstalován nějaký SW a tím jsem tady skončil a dále nepokračoval.

Akt 2 – našel jsem si návod jak „hacknout“ přihlašovací obrazovku
Jedná se o způsob, kdy si na počítači spustím live CD s linuxem a vyměním program „Utilman“ za „cmd“ a pak z přihlašovací obrazovky si změním heslo. Ale jak to udělat, když zrovna nemám po ruce druhý počítač, kde bych si vytvořil live CD/USB. Ale…

Akt 3 – …ale provozuji Windows na MAC mini přes BOOTCAMP, takže se připojím na MAC OSx a bude to…
Než jsem si vzpomněl jak přebootovat což šlo až po výměně klávesnice DELL za Apple a následně jsem se v MAC OSx zorientoval, tak uplynulo pár desítek minut času. Následně jsem si stáhl ISO soubor Ubuntu a dle návodu na stránkách (ubuntu.com) jsem si vytvořil boot USB disk. Samozřejmě, aby to nebylo tak jednoduché, jsem i zde vlastní vinou ztratil pár minut (špatně jsem si přečetl hlášku).

Akt 4 – nabootování Ubuntu
Boot proběhl v pořádku, přejmenování souborů taky (až jsem se divil, že v tomto kroku žádný zádrhel).

Akt 5
Tak nabootuji do Windows a spadne mi úsměv ve chvíli, kdy se přihlašovací dialog nezobrazí a Windows nastartují jako by se celý incident nestal. Ach jo :(

Epilog
Nevím co napsat… …udělejte si obrázek sami.

Fotoalbum v NETTE (flickr style) – práce s fotkami

Myšlena práce s fotkami na straně serveru…

Co bylo třeba vyřešit:

  1. zmenšení fotek
  2. záloha velkých originálů
  3. vytvoření náhledů
  4. vytvoření úvodní fotky pro danou složku
  5. (vyplynulo až později) „kešování“ dat, pro dostatečně rychlé procházení fotoalbem

Všechno se děje vlastně sériově. Vždy se ověří, zda danou operaci je nutné dělat a pokud ne, tak se vynechá. Zároveň se dané části kešují, jelikož z praxe vyplynulo, že i když není třeba nic dělat, tak samotné ověřování trvá řády jednotek až desítek sekund. A na to nikdo čekat nechce.

Poznámka: Vím, že kód níže není optimální a zasloužil by jistou refaktorizaci, ale to bych tento článek a především fotoalbum nenapsal ani za rok (Ano píšu ho ve svém volném čase a pro dobro věci. Ne pro peníze.). A taky se omlouvám za ne úplně 100% prezentaci kódu, ale neznám lepší plugin do WordPressu, který by to zvládal dokonale.

# VYTÁHNEMKE SI SLOŽKY A K NIM ÚVODNÍ FOTKU (samozřejmě jen pokud již nejsou v cache)
$value = $cache->load("folders-{$path}");
if ($value === NULL) {
	$i=0;
	foreach (Finder::findDirectories('*')->exclude('[0-9][0-9][0-9]', '_big')->in($path)->orderByName('DESC') as $key => $file) {
		$this->template->dirs[$i]['name'] = $this->I->getNameFromPath(substr($key, strlen(ALBUM_PATH)).'/');
		// vytahneme si small_home
		foreach (Finder::findFiles('_small_home.png')->in($key) as $key_home => $file_home) {
			$this->template->dirs[$i]['small_home'] = ALBUM_DIR . substr($file_home->getPath(), strlen(ALBUM_PATH)) . '/_small_home.png';
		}
		$this->template->dirs[$i++]['pos'] = substr($key, strlen(ALBUM_PATH)+5);
	}
	$cache->save("folders-{$path}", $this->template->dirs);
} else {
	$this->template->dirs = $value;
}
 
# VYTÁHNEMKE SI FOTKY (samozřejmě jen pokud již nejsou v cache)
$value = $cache->load("files-f-{$path}");
if ($value === NULL) {
	# POKUD EXISTUJÍ TAKOVÉ, KTERÉ MAJÍ VĚTŠÍ STRANU VĚTŠÍ NEŽ 1200PX, TAK JE:
	#	- ZMENŠÍME
	# 	- VELKÉ ORIGINÁLY ZAZÁLOHUJEME
	# 	- PŘENESEME EXIF INFORMACE			
	foreach (Finder::findFiles('*.jpg', '*.jpeg')->exclude('*_small*')->dimensions('>1200', '>1200')->in($path) as $key => $file) {
		$image = @Image::fromFile($key); // ini_set(‘gd.jpeg_ignore_warning’, 1);
 
		$image->resize(1200, 1200, Image::SHRINK_ONLY | Image::FIT);
 
		$ex = exif_read_data($key, 'EXIF');
		if(!empty($ex['Orientation'])) {
			switch($ex['Orientation']) {
				case 8:
					$image->rotate(90, 0);
					break;
				case 3:
					$image->rotate(180, 0);
					break;
				case 6:
					$image->rotate(-90, 0);
					break;
			}
		}
 
		$image->sharpen();
 
		$s = $key;
		$d = $file->getPath() . '/_big/' . $file->getFilename();
		$fd = new SplFileInfo($file->getPath() . '/_big/');
		if (!$fd->isDir()) mkdir($file->getPath() . '/_big/');
		rename($s, $d);
		$image->save($key);
 
		// přeuložíme EXIF informace - použita externí knihovna PelJpeg
		$input_jpeg = new PelJpeg($d);
		$exif = $input_jpeg->getExif();
		if ($exif != null) {
			$output_jpeg = new PelJpeg($key);
			$output_jpeg->setExif($exif);
			$output_jpeg->saveFile($key);
		}
	}
 
	# DÁLE VYTVOŘÍME MALÉ NÁHLEDY
	foreach (Finder::findFiles('*.jpg', '*.jpeg')->exclude('*_small*')->exclude($denyFiles)
		->filter(function($file) {
			$file_extension = pathinfo($file->getFilename(), PATHINFO_EXTENSION);
			$info = new SplFileInfo($file->getPath() . '/' . $file->getBasename('.' . $file_extension) . '_small_500' . '.jpg');
			return !$info->isFile();
		})->in($path) as $key => $file) {
 
		// vytvoření náhledu
		$image = @Image::fromFile($key);
		$image->resize(500, 500, Image::SHRINK_ONLY | Image::FIT);
 
		$image->sharpen();
		$file_extension = pathinfo($file->getFilename(), PATHINFO_EXTENSION);
		$image->save($file->getPath() . '/' . $file->getBasename('.' . $file_extension) . '_small_500' . '.jpg');
	}
 
	# DÁLE VYTVOŘÍME ÚVODNÍ NÁHLED, KTERÝ SE BUDE ZOBRAZOVAT MÍSTO OBRÁZKU SLOŽKY
	$small_home = Image::fromBlank(360, 360, Image::rgb(255, 255, 255, 127));
	$i=0;
	foreach (Finder::findFiles('*.jpg', '*.jpeg')->exclude('*_small*')->exclude($denyFiles)
		->filter(function($file) {
			$info = new SplFileInfo($file->getPath() . '/_small_home.png');
			return !$info->isFile();
		})->in($path) as $key => $file) {
		$image = @Image::fromFile($key);
		$image->saveAlpha(true);
		$image->resize(250, 250, Image::SHRINK_ONLY | Image::FIT);
		$blank = Image::fromBlank($image->getWidth()+20, $image->getHeight()+20, Image::rgb(255, 255, 255));
		$blank->alphaBlending(true);
		$blank->saveAlpha(true);
 
		$blank->place($image, 10, 10);
 
		$blank->rotate(mt_rand(-60, 60), Image::rgb(255, 255, 255, 127));
		$blank->sharpen();
 
		$small_home->place($blank, 20+mt_rand(-15, 15), 20+mt_rand(-15, 15));
 
		if ($i++ == 5) {
			$small_home->resize(200, 200, Image::SHRINK_ONLY | Image::FIT);
			$small_home->save($file->getPath() . '/_small_home.png', NULL, Image::PNG);
 
			$upPath = substr($file->getPath(), 0, strrpos($file->getPath(), '/')+1);
			$cache->remove("folders-{$upPath}"); // pokud tvoříme náhled, tak smažeme cache předka složky, aby se mi při dalším načtení tato cache obnovila
			break;
		}
	}
 
	# A KDYŽ MÁME VŠECHNO PŘIPRAVENO, TAK SI JEŠTĚ JEDNOU NAČTU KOMPLETNÍ INFO, KTERÉ SI PAK ULOŽÍM DO CACHE
	$i=0;
	foreach (Finder::findFiles('*.jpg', '*.jpeg')->exclude('*_small*')->exclude($denyFiles)->in($path)->orderByEXIFTime() as $key => $file) {
		$this->template->files[$i]['key'] = ALBUM_DIR . substr($key, strlen(ALBUM_PATH));
		$exif_ifd0 = read_exif_data($file, 'IFD0', 0);
		$this->template->files[$i]['file'] = date("d.m.y H:i", strtotime($exif_ifd0['DateTime']));
 
		$file_extension = pathinfo($file->getFilename(), PATHINFO_EXTENSION);
		$tf = @Image::fromFile($file->getPath() . '/' . $file->getBasename('.' . $file_extension) . '_small_500' . '.jpg');
		$this->template->files[$i]['w'] = $tf->getWidth(); 
		$this->template->files[$i]['h'] = $tf->getHeight(); 
		$this->template->files[$i++]['pos'] = ALBUM_DIR . substr($file->getPath(), strlen(ALBUM_PATH)) . '/' . $file->getBasename('.' . $file_extension) . '_small_500' . '.jpg';
	}
	$cache->save("files-f-{$path}", $this->template->files);
} else {
	$this->template->files = $value;
}

Jednotlivé části dle mě nemá smysl detailněji popisovat. Je třeba se tím prokousat a pochopit. Pokud by byly dotazy nechť poslouží komentáře.

Jedna konstrukce by šla napsat jednodušeji, jen DEBIAN v současnosti neobsahuje správnou verzi PHP a v té co tam je obsahuje známou chybu…

// Takže místo...
$tf = @Image::fromFile($file->getPath() . '/' . $file->getBasename('.' . $file->getExtension()) . '_small_500' . '.jpg');
// musím používat toto...
$file_extension = pathinfo($file->getFilename(), PATHINFO_EXTENSION);
$tf = @Image::fromFile($file->getPath() . '/' . $file->getBasename('.' . $file_extension) . '_small_500' . '.jpg');

Fotoalbum v NETTE (flickr style) – příprava složek

V této kapitole prezentuji, jak jsem musel připravit stromovou strukturu současného fotoalba.
webalizace – odstranění diakritiky a nahrazení speciálních znaků (např. mezer) pomlčkou

Výchozí stav:
Struktura složek ve formátu /ALBUM/001/001/038/019/. Tuto strukturu jsem měl uloženou v databázi ve formátu (id, parent_id, path, description). Tuto strukturu jsem dále rozšířil o atributy level (hloubka zanoření ve stromu), webalize-desc )(webalizovaný název složky), webalize-path (webalizovaná cesta).

1. krok – vytvoření webalizovaného popisku v databázi

	public function createCoolURL() {
		$this->connection->exec('begin');
		foreach ($this->connection->table('I_dir') as $row) {
			$this->connection->exec('UPDATE `I_dir` SET `webalize-desc`=? WHERE `id`=? LIMIT 1', Strings::webalize($row->desc, '._', FALSE), $row->id);
		}
		$this->connection->exec('commit');
	}

2. krok – vypočtení atributu level

	public function computeLevel() {
		$this->connection->exec('begin');
		foreach ($this->connection->table('I_dir') as $row) {
			$this->connection->exec('UPDATE `I_dir` SET `level`=? WHERE `id`=? LIMIT 1', substr_count ($row->path, '\\')-1, $row->id);
		}
		$this->connection->exec('commit');
	}

3. krok – vytvoření webalizovaného překladu cesty
Tady je třeba se zamyslet co vlastně děláme a proč to níže může vypadat relativně složitě. Výchozí stav je takový, že známe cestu k cílové složce (/001/001/038/019/) a její název. Ale abychom byli schopni univerzálně přejmenovat celou cestu, tak musíme začít od kořene. Tj. první přejmenovat složky level=1 (/001/). Následně level=2 (/001/001/) a to pomocí parent_id, kde již víme, že se ta složka nějak jmenuje a jen připojit webalizovaný název aktuální úrovně.

	public function createWebalizePath() {
		$this->connection->exec('begin');
		foreach ($this->connection->fetchAll('SELECT DISTINCT `level` FROM `I_dir` WHERE `level`>0 ORDER BY `level` ASC') as $l) {
			foreach ($this->connection->fetchAll('SELECT `a`.`id`, `a`.`webalize-desc` AS `wd`, `b`.`webalize-path` AS `path` FROM `I_dir` `a` JOIN `I_dir` `b` ON `a`.`parent_id`=`b`.`id` WHERE `a`.`level`=?', $l->level) as $row) {
				$this->connection->exec('UPDATE `I_dir` SET `webalize-path`=? WHERE `id`=? LIMIT 1', ($row->path.$row->wd.'\\'), $row->id);
			}
		}
		$this->connection->exec('commit');
	}

4. krok – přejmenování samotných složek
Záhadná formule REVERSE(SUBSTRING(REVERSE(`path`), 5))

  • REVERSE(`path`) z „\001\002\“ do „\200\100\“
  • SUBSTRING(…, 5) z „\200\100\“ do „100\“
  • REVERSE(…) z „100\“ do „\001“

Zbytek je snad jasný…

	public function renameFolders() {
		foreach ($this->connection->fetchAll('SELECT `path`, REVERSE(SUBSTRING(REVERSE(`path`), 5)) AS `newPath`, `webalize-desc` AS `wd` FROM `I_dir` WHERE `level`>0 ORDER BY `level` DESC') as $row) {
			@rename(ALBUM_PATH . strtr($row->path, '\\', '/'), ALBUM_PATH . strtr($row->newPath, '\\', '/') . $row->wd);
		}
	}

Zatím tu není řešeno přidání nové složky, ale to bude principiálně stejné. Jen budu muset vyřešit jak dám programu vědět, která složka a kde přibyla.
Nápady – databáze (chci se ji maximálně vyhnout a velká náchylnost na chybu), Nějaký FLAG soubor – zatím se mi jeví jako dobré řešení. Musel bych jen udělat „robota“, který by hledal soubor jistých parametrů a v případě nálezu by danou složku zpracoval a soubor třeba smazal. Ale to předbíhám…

Fotoalbum v NETTE (flickr style)

Již delší dobu plánuji, že přepíšu dávno nevyhovující fotoalbum oddílu BVÚ.

Nevýhody současného alba:
Viditelné:

  • malé náhledy (generuje je aplikace)
  • v případě aktualizace fotek, není dostupné
  • závislost na datumu souboru (pokud se nepovedlo nahrání, bylo tam nesmyslné datum)

Neviditelné:

  • složité nahrávání fotek (přes již prastarou aplikaci Olympus Camedia Photoalbum)
  • proprietální databáze Olympus Camedia Photoalbum postavená na MS ACCESS 1997, bez rozumné možnosti rozšíření
  • nesnadná správa stránek v prostředí MS – ASP (VB SCRIPT)
  • závislost na datumu souboru – musel se dodržovat speciální postup jinak se o datum přišlo, případně se musel synchronizovat s EXIF informacemi

Co čekám od nového alba:
Vyditelné:

  • rozumné rozhraní odpovídající době
  • možnost větší interakce se stránkami – TOPování fotek, komentáře, TAGy, …
  • větší propojení stránek oddílu (www, stream, fotoalbum)

Neviditelné:

  • sjednocení platforem webů
  • moderní programátorské prostředí (NETTE, jQuery)
  • lepší správa, menší závislost na proprietálním prostředí

Požadavky na nové fotoalbum:
Vyditelné:

  • rychlost
  • přístupnost
  • maximální orientace na prohlížení fotek – je to fotoalbum že!

Neviditelné:

  • minimální závislost na databázi
  • maximální jednoduchost nahrávání fotek

Pro představu uvedu postupy nahrávání fotek:
Postaru:

  • stáhnout fotky v něčem co nemění datumy souborů
  • protřídit
  • zmenšit v něčem co nemění datumy souborů
  • zabalit do ZIP archívu – aby při nahrávání na FTP se nezměnily datumy souborů
  • nahrát archiv na FTP
  • přes vzdálenou plochu na serveru v aplikaci Olympus Camedia Photoalbum vytvořit složku – od otevření aplikace je fotoalbum na webu nedostupné, jelikož si aplikace drží výhradní přístup
  • najít na disku vytvořenou složku (stromová struktura má formát: /ALBUM/001/001/038/019/)
  • nakopírovat fotky z archivu
  • otevřít vytvořené album ve Olympus Camedia Photoalbum – dojde k pomalému vytvoření náhledu a indexaci fotek
  • dříve se ještě v rámci speciální aplikace řešily publikované fotky, to již před časem odpadlo…
  • velké originály popsat a zazálohovat
  • Nově:

    • stáhnout fotky
    • protřídit
    • nahrát na FTP do pojmenované složky (tady ještě uvažuji o použití SAMBA protokolu a ukládání fotek na Windows share, který je pro běžné uživatele dostupnější)

    Úspora kroků je zřejmá!

    Na co se můžete těšit v dalších kapitolách?

    • zpracování fotek – od nahrání až po prezentaci
    • prezentace fotek na celou obrazovku prohlížeče – kdo zná flickr ví o čem píšu

    Migrace aplikace z NETTE 0.9x na NETTE 2.0x

    Potřeboval jsem v jedné starší aplikaci použít dynamické snippety. Po tom co jsem delší dobu hledal jak toto vyřešit ve starém NETTE 0.96, tak jsem na tuto myšlenku nakonec rezignoval a rozhodl jsem se přejít na aktuální verzi NETTE 2.0.6. I když ze začátku jsem se toho bál, tak nakonec to nebylo tak hrozné…

    Co jsem tedy měnil…

    bootstrap.php – ten doznal asi největších změn, ale jedná se spíše jen o přepis než o změnu logiky
    OLD:

    < ?php
    require LIBS_DIR . '/Nette/loader.php';
    require LIBS_DIR . '/dibi/dibi.php';
    require LIBS_DIR . '/currency.php';
    require APP_DIR  . '/models/MyAuthenticator.php';
    require APP_DIR  . '/models/FrontAuthenticator.php';
     
    date_default_timezone_set('Europe/Prague');
     
    //$mode = Debug::DEVELOPMENT;
    $mode = Debug::PRODUCTION;
    Debug::enable($mode, NULL, 'mail@na-me.cz');
    Debug::$strictMode = TRUE;
     
    $loader = new RobotLoader();
    $loader->addDirectory(APP_DIR);
    $loader->register();
     
    // nastavime dibi
    dibi::connect(array(
    	'driver'   => 'mysql',
    	'host'     => 'localhost',
    	'username' => 'user',
    	'password' => 'pass',
    	'database' => 'db',
    	'charset'  => 'utf8',
     ));
     
    $application = Environment::getApplication();
    $application->errorPresenter = 'Error';
    $application->catchExceptions = TRUE;
     
    $router = $application->getRouter();
     
    $router[] = new Route('index.php', array(
    	'presenter' => 'Default',
    	'action' => 'default',
    ), Route::ONE_WAY);
     
    $router[] = new Route('/', array(
        'presenter' => 'Default',
        'action' => 'default',
    ));
     
    $router[] = new Route('admin/<action>/', array(
        'presenter' => 'Admin',
        'action' => 'objednavky',
    ));
     
    // další routy
     
    FormContainer::extensionMethod('FormContainer::addCheckboxList', array('CheckboxList', 'addCheckboxList'));
     
    $application->run();

    NEW:

    < ?php
    require LIBS_DIR . '/Nette/loader.php';
    require LIBS_DIR . '/dibi/dibi.php';
    require LIBS_DIR . '/currency.php';
    require APP_DIR  . '/models/MyAuthenticator.php';
    require APP_DIR  . '/models/FrontAuthenticator.php';
     
    date_default_timezone_set('Europe/Prague');
     
    $configurator = new Configurator;
     
    $configurator->setDebugMode(TRUE);
    $configurator->enableDebugger(dirname(__FILE__) . '/log');
     
    // Enable RobotLoader - this will load all classes automatically
    $configurator->setTempDirectory(dirname(__FILE__) . '/temp');
    $configurator->createRobotLoader()
    	->addDirectory(APP_DIR)
    	->addDirectory(LIBS_DIR)
    	->register();
     
    // Create Dependency Injection container from config.neon file
    //$configurator->addConfig(dirname(__FILE__) . '/config/config.neon');
     
    $container = $configurator->createContainer();
     
    // Setup router
    $container->router[] = new Route('index.php', array(
    	'presenter' => 'Default',
    	'action' => 'default',
    ), Route::ONE_WAY);
     
    $container->router[] = new Route('/', array(
        'presenter' => 'Default',
        'action' => 'default',
    ));
     
    $container->router[] = new Route('admin/<action>/', array(
        'presenter' => 'Admin',
        'action' => 'objednavky',
    ));
     
    // další routy
     
    // nastavime dibi
    dibi::connect(array(
    	'driver'   => 'mysql',
    	'host'     => 'localhost',
    	'username' => 'user',
    	'password' => 'pass',
    	'database' => 'db',
    	'charset'  => 'utf8',
    	'profiler' => TRUE
     ));
     
    FormContainer::extensionMethod('FormContainer::addCheckboxList', array('CheckboxList', 'addCheckboxList'));
     
    $container->application->run();

    Největším oříškem pro mě bylo rozchodit LATTE šablony. Ne že by to bylo v novém NETTE složité ale …
    BasePresenter.php
    OLD

    	// v 0.96 si musíme použití šablony zajistit registrací tohoto filtru
    	public function templatePrepareFilters($template) {
    		$template->registerFilter(new CurlyBracketsFilter);
    	}

    NEW

    	// v novém NETTE jsem myslel, že bude stačit
    	// zakomentovat jeden řádek ($template->register...),
    	// ale to nefungovalo a několik minut jsem nemohl přijít na to proč.
    	// Nakonec pomohlo zakomentovat celou tuto funkci.
    	/*public function templatePrepareFilters($template) {
    		$template->registerFilter(new CurlyBracketsFilter);
    	}*/

    Další změny jsou spíše drobnosti a řešil jsem je jak jsem na ně přicházel:
    $form->addFile() -> $form->addUpload
    User::INACTIVITY -> IUserStorage::INACTIVITY
    $user->setAuthenticationHandler(new MyAuthenticator); -> $user->setAuthenticator(new MyAuthenticator);
    $form->onSubmit[] -> $form->onSuccess[]
    skipFirst() -> setPrompt()
    třída String -> Strings (například Strings::webalize())

    Takže nakonec žádné terno… :)

    Porovnání spořících účtů – ZUNO, AXA, EQUA, AIR BANK

    Již několik let používám spořící účet od AXA BANK. Jelikož se jedná o spoření, kde nepotřebuji dnes a denně bojovat s internetovým bankovnictvím (IB), tak se nedá říct, že ta spousta „maličkostí“, které mě na jejím IB štvou by mě vyváděla z míry…

    Ale…

    Ale najednou se tu začaly jak houby po dešti množit nové internetové banky.

    AIR/BANK
    AXA
    EQUA BANK
    ZUNO

    mBanku záměrhně nezmiňuji, jelikož ji vůbec neřadím jako konkurenci pro její nesmyslné zesložiťování podmínek pro výhodné spoření.

    Tak jsem se začal pídit po tom, co mi která banka nabídne. A jelikož jsem nikde nenašel rozumnou kalkulačku, tak jsem si takovou sestavil.

    Kalkulačka výhodnosti spořících účtů.