Author Archive

Czy Facebook umożliwia kierowanie pojazdem?

Author: Marcin Dryka (drymek) | luty 10th, 2010
avatar

Okazuje się, że tak! Zabawna historia. Wczoraj przechodząc od auta, do miejsca mojej codziennej pracy, wypadło mi prawo jazdy wprost na chodnik. W historii nie byłoby nic nadzwyczajnego – należałoby tylko zgłosić na policję fakt zagubienia, zapłacić 75zł za wyrobienie nowego dokumentu i koniec sprawy – gdyby nie to, że miły znalazca, Pan Przemek, znalazł również mój profil na Facebooku i zostawił mi wiadomość, że to właśnie on je odnalazł. Zaproponował tym samym, że w dniu dzisiejszym będzie w tym samym miejscu i może mi je przekazać. Tak też się stało, dokument wrócił do mnie cały i bezpieczny, a ja mogę się już nie obawiać kontroli policyjnej.

Znalazcy chciałbym jeszcze raz bardzo podziękować: dziękuję!

Dlaczego kocham PHP!

Author: Marcin Dryka (drymek) | listopad 19th, 2009
avatar

Każdego programistę w życiu spotykają chwile doszukiwania się logiki lub chwile zwątpienia w zasady matematyki. Język PHP w znaczącym stopniu ułatwia zaistnienie takiej sytuacji.
Wyobraźmy sobie, potrzebę napisania klasy umożliwiającej stronicowanie wyników. Metoda calculate służy do policzenia dolnego zakresu wyników (strona 5, wyniki 51-60). Całość wygląda następująco:

class Pager
{
  private
    $page = 1,
    $per_page = 10;

  public function calculate()
  {
    return $this->page * $this->per_page - $this-per_page + 1;
  }
}

Jedna mała pomyłka. Zamiast

$this->per_page

do kodu wkradło się:

$this-per_page

Co to zmienia? Na pierwszy rzut oka parser PHP powinien poinformować o błędzie… jednak linia kodu zawierająca błąd interpretowana jest następująco:

$this->page // 1
$this->per_page // 10
$this  // rzutowane na int to 1, bo obiekt istnieje
per_page // jako stała niezdefiniowana to 0

Wykonywane działanie matematyczne wygląda zatem następująco:

1 * 10 - 1 - 0 + 1

Wynik działania to o dziwo 10… Dlatego kocham PHP!

Demotywacja demotywatorów II

Author: Marcin Dryka (drymek) | październik 19th, 2009
avatar

« Poprzednia część.
Już po krótkim używaniu skryptu, nie trudno się zorientować, że działa on zbyt wolno. Aby przyspieszyć działanie można użyć zachowywania informacji o już ściągniętych demotywatorach. Zatem tekst o tym jak dodać cache:
Klasa Feed. Różni się nieznacznie od oryginału – należy dodać cztery nowe metody:

  public function addToCache($page_url, $image_url)
  {
    Debug::Log("Adding to cache");
    $this->cache[$page_url] = $image_url;
  }

  public function loadCache()
  {
    Debug::Log('Reading cache');
    if (file_exists("cache.csv"))
    {
      $fh = fopen("cache.csv", "r");
      while (($data = fgetcsv($fh, 1000, ",")) !== FALSE)
      {
        $this->cache[$data[0]] = $data[1];
      }
      fclose($fh);
      return $this->cache;
    }
  }

  public function saveCache()
  {
    Debug::Log("Saving cache");
    $fh = fopen('cache.csv', 'w');
    foreach ($this->cache as $page_url=>$image_url)
    {
      fputcsv($fh, array($page_url, $image_url));
    }
    fclose($fh);
  }

  public function isCached($page_url)
  {
    Debug::Log("Checking cache");
    if (empty($this->cache))
    {
      $this->loadCache();
    }
    if (isset($this->cache[$page_url]))
    {
      Debug::Log("Found in cache");
      return $this->cache[$page_url];
    }
    Debug::Log("Not found in cache");
    return false;
  }

oraz użyć ich w odpowiedni sposób. Cały plik wygląda następująco:

url = $url;
  }

  /*
   *  Read remote rss file
   */
  public function getOrginalContent()
  {
    if (is_null($this->url))
    {
      throw new FeedException("You have to set url first!");
    }

    $this->orginalContent = Remote::get_remote_file_content($this->url);
    return $this->orginalContent;
  }

  /*
   * Create a DOMDocument from rss.xml file
   */
  public function getDomOrginalContent()
  {
    if (false === $this->orginalContent || is_null($this->orginalContent))
    {
      throw new FeedException("Can not download rss file");
    }

    $domDocument = new DOMDocument();
    $domDocument->loadXML($this->orginalContent);
    return $domDocument;
  }

  public function addToCache($page_url, $image_url)
  {
    Debug::Log("Adding to cache");
    $this->cache[$page_url] = $image_url;
  }

  public function loadCache()
  {
    Debug::Log('Reading cache');
    if (file_exists("cache.csv"))
    {
      $fh = fopen("cache.csv", "r");
      while (($data = fgetcsv($fh, 1000, ",")) !== FALSE)
      {
        $this->cache[$data[0]] = $data[1];
      }
      fclose($fh);
      return $this->cache;
    }
  }

  public function saveCache()
  {
    Debug::Log("Saving cache");
    $fh = fopen('cache.csv', 'w');
    foreach ($this->cache as $page_url=>$image_url)
    {
      fputcsv($fh, array($page_url, $image_url));
    }
    fclose($fh);
  }

  public function isCached($page_url)
  {
    Debug::Log("Checking cache");
    if (empty($this->cache))
    {
      $this->loadCache();
    }
    if (isset($this->cache[$page_url]))
    {
      Debug::Log("Found in cache");
      return $this->cache[$page_url];
    }
    Debug::Log("Not found in cache");
    return false;
  }
  /*
   * Get image URL from remote webpage
   */
  public function getImage($item)
  {
    if ('link' == $item->nodeName)
    {
      if ($cache = $this->isCached($item->nodeValue))
      {
        return $cache;
      }
      $htmlContent = Remote::get_remote_file_content($item->nodeValue);
      if (false !== $htmlContent)
      {
        //Search image tag
        $pattern = "/bigurl=\".*\"/";
        preg_match($pattern, $htmlContent, $urls);

        //Finally url to the image
        $url = substr($urls[0], 8, -1);
        $this->addToCache($item->nodeValue, $url);
        return $url;
      }
      return 'http://demotywatory.pl/res/img/demotywatory-logo.gif';
    }
  }

  /*
   * Set new description for current item
   */
  public function setImage($item)
  {
    //Find element link
    foreach ($item->childNodes as $rssDomItemChild)
    {
      if ('link' == $rssDomItemChild->nodeName)
      {
        //Download demot page
        $url = $this->getImage($rssDomItemChild);
        break;
      }
    }

    //Find element description
    foreach ($item->childNodes as $rssDomItemChild)
    {
      if ('description' == $rssDomItemChild->nodeName)
      {
        $rssDomItemChild->nodeValue = '<img src="'.$url.'">';
        break;
      }
    }
    Debug::Log('Found url: ['.$url.']');
  }

  /*
   * Convert rss.xml to rss.xml with images in description
   */
  public function transform()
  {
    //Get DOMDocument
    $this->transformedDom = $this->getDomOrginalContent();
    //Find all items
    $rssItems = $this->transformedDom->getElementsByTagName('item');

    //Foreach feed
    Debug::Log('Starting retriving feeds');

    foreach ($rssItems as $rssItem)
    {
      //Set image in description for node
      $this->setImage($rssItem);
    }
    Debug::Log('Finished retriving feeds!');
  }

  /*
   * Get transformed XML
   */
  public function getResults()
  {
    return $this->transformedDom->saveXml();
  }

  /*
   * Save results to file (xml)
   */
  public function save($filename)
  {
    Debug::Log('Saving file ('.$filename.')');
    $fh = fopen($filename, 'w');
    if (!$fh)
    {
      throw new FeedException('Can not open file ('.$filename.') to write!');
    }
    fwrite($fh, $this->getResults());
    fclose($fh);
  }
}

Należy jeszcze zmienić skrypt wywołujący, czyli index.php:

setUrl('http://demotywatory.pl/rss.xml');
//Get rss file content
$feed->getOrginalContent();
//Convert to new format
$feed->transform();
//Save to file
$feed->save('result.xml');
$feed->saveCache();

I to wszystko. Teraz skrypt pobierze informacje jedynie o nowych rssach. Informacje które już posiada w razie potrzeby zostaną użyte ponownie.
Efekt działania można zobaczyć na http://drymek.hostei.com/.
Gotowy do użycia plik rss znajduje się tutaj: http://feeds.feedburner.com/demotywatorator?format=xml

Zestaw wszystkich plików do ściagnięcia: Demotywatorator2

Demotywacja demotywatorów

Author: Marcin Dryka (drymek) | październik 16th, 2009
avatar

Demotywatory.pl, znany polski serwis humorystyczny. Jednak serwis ma jedną niedogodność – trzeba wchodzić na strone internetową aby zobaczyć obrazki. Podobnie w przypadku kanału RSS – nie ma możliwości obejrzenia obrazka w treści wiadomości. Jest problem jest i fix:
5 plików rozwiązujących problem:

  1. Remote.php
  2. FeedException.php
  3. Debug.php
  4. Feed.php
  5. index.php

Pierwszy z nich, rozbudowany aż nadto co powinien być, jednak faza testowania wymagała porównania prędkości działania kilku rozwiązań. Curl został domyślnym, ze względu na jego szybkość i niezawodność.

<?php

/*
 * Read remote file with some methods
 *
 * @author drymek
 *
 */
class Remote
{
  /*
   * Read file with default method
   */
  public static function get_remote_file_content($url)
  {
    return self::get_with_curl($url);
  }

  /*
   * Curl - default method for Remote
   */
  public function get_with_curl($url)
  {
    // create a new cURL resource
    $ch = curl_init();

    // set URL and other appropriate options
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    // grab URL and pass it to the browser
    $content = curl_exec($ch);

    // close cURL resource, and free up system resources
    curl_close($ch);
    return $content;
  }

  /*
   * file_get_contents use buildin PHP function
   */
  public function get_with_file_get_contents($url)
  {
    return file_get_contents($url);
  }

  /*
   * fopen and fgets functions to read file
   */
  public function get_with_fopen($url)
  {
    $fh = fopen($url, 'rb');
    if (!$fh)
    {
      return '';
    }

    $content = '';
    while (!feof($fh))
    {
      $content .= fgets($fh, 1024);
    }
    fclose($fh);
    return $content;
  }
}

Drugi z plików zapewnia jedynie możliwość wyrzucania wyjątków z klasy Feed.

<?php
/*
 * Feed Exception class
 *
 * @author drymek
 *
 */
class FeedException extends Exception
{
}

Klasa Debug umożliwia wypisywanie informacji kontrolnych podczas pisania kodu:

<?php
/*
 * Simple debug class
 *
 * @author drymek
 *
 */
class Debug
{
  /*
   * Print debug message on default output
   */
  public static function Log($message)
  {
    //Check debug mode
    if (DEBUG)
    {
      echo $message."\n";
    }
  }
}

Klasa Feed! Czyli mięso właściwe. To co pobiera wszystkie informacje, przetwarza je i zapisuje:

<?php
require_once 'Remote.php';        //Read remote files
require_once 'FeedException.php'; //Implementation of Feed Exception
require_once 'Debug.php';         //Debug message

/*
 * Class to convert demotywatory.pl rss
 *
 * @author drymek
 *
 */
class Feed
{
  private
    $url,             //rss.xml file url
    $orginalContent,  //rss.xml file content
    $transformedDom;  //rss.xml file DOMDocument

  /*
   * Set feed url
   */
  public function setUrl($url)
  {
    $this->url = $url;
  }

  /*
   *  Read remote rss file
   */
  public function getOrginalContent()
  {
    if (is_null($this->url))
    {
      throw new FeedException("You have to set url first!");
    }

    $this->orginalContent = Remote::get_remote_file_content($this->url);
    return $this->orginalContent;
  }

  /*
   * Create a DOMDocument from rss.xml file
   */
  public function getDomOrginalContent()
  {
    if (false === $this->orginalContent || is_null($this->orginalContent))
    {
      throw new FeedException("Can not download rss file");
    }

    $domDocument = new DOMDocument();
    $domDocument->loadXML($this->orginalContent);
    return $domDocument;
  }

  /*
   * Get image URL from remote webpage
   */
  public function getImage($item)
  {
    if ('link' == $item->nodeName)
    {
      $htmlContent = Remote::get_remote_file_content($item->nodeValue);
      if (false !== $htmlContent)
      {
        //Search image tag
        $pattern = "/bigurl=\".*\"/";
        preg_match($pattern, $htmlContent, $urls);

        //Finally url to the image
        return substr($urls[0], 8, -1);
      }
      return 'http://demotywatory.pl/res/img/demotywatory-logo.gif';
    }
  }

  /*
   * Set new description for current item
   */
  public function setImage($item)
  {
    //Find element link
    foreach ($item->childNodes as $rssDomItemChild)
    {
      if ('link' == $rssDomItemChild->nodeName)
      {
        //Download demot page
        $url = $this->getImage($rssDomItemChild);
        break;
      }
    }

    //Find element description
    foreach ($item->childNodes as $rssDomItemChild)
    {
      if ('description' == $rssDomItemChild->nodeName)
      {
        $rssDomItemChild->nodeValue = '<img src="'.$url.'">';
        break;
      }
    }
    Debug::Log('Found url: ['.$url.']');
  }

  /*
   * Convert rss.xml to rss.xml with images in description
   */
  public function transform()
  {
    //Get DOMDocument
    $this->transformedDom = $this->getDomOrginalContent();
    //Find all items
    $rssItems = $this->transformedDom->getElementsByTagName('item');

    //Foreach feed
    Debug::Log('Starting retriving feeds');

    foreach ($rssItems as $rssItem)
    {
      //Set image in description for node
      $this->setImage($rssItem);
    }
    Debug::Log('Finished retriving feeds!');
  }

  /*
   * Get transformed XML
   */
  public function getResults()
  {
    return $this->transformedDom->saveXml();
  }

  /*
   * Save results to file (xml)
   */
  public function save($filename)
  {
    Debug::Log('Saving file ('.$filename.')');
    $fh = fopen($filename, 'w');
    if (!$fh)
    {
      throw new FeedException('Can not open file ('.$filename.') to write!');
    }
    fwrite($fh, $this->getResults());
    fclose($fh);
  }
}

I ostatni z plików który wszystko uruchamia:

<?php
define('DEBUG', true);    //Set debug on
require_once 'Feed.php';  //Include Feed class

//Create new Feed instance
$feed = new Feed();
//Set feed url
$feed->setUrl('http://demotywatory.pl/rss.xml');
//Get rss file content
$feed->getOrginalContent();
//Convert to new format
$feed->transform();
//Save to file
$feed->save('result.xml');

Teraz wystarczy umieścić pliki na serwerze lub jeśli takiego nie posiadamy to można skorzytać np. z hostei.com (webhosting umożliwiający otwieranie plikow curl/fopen).
I magia! Mamy rss z obrazkami demotywatorów w wiadomości! (Należy oczywiście odświeżać co jakiś czas nasze domotywatory, jednak użycie crona i wgeta w tej sytuacji jest w 100% wystarczające

Demotywatorator

« Druga część.