Kirjautuminen

Haku

Tehtävät

Kilpailu

Murra koodi!
Lue ja osallistu!
Seuraava vihje 29.1.
Voittajia 1 + yrittäjiä 1

Koodit: PHP: Kirjautuminen toiselle sivustolle cURLilla

Kirjoittaja: Metabolix

Kirjoitettu: 05.01.2011 – 13.10.2019

Tagit: kirjaston käyttö, vinkki

PHP:llä on usein hyödyllistä hakea tietoa muilta sivuilta tai lähettää sitä. Mutta mitä tehdä, jos tutkittava sivusto vaatii kirjautumisen? Vaihtoehtoja on muutama: Jos kirjautuminen on toteutettu sopivalla tavalla, tavallinen file_get_contents höystettynä stream_context_create-funktiolla riittää. Toinen mahdollisuus on yhdistää suoraan palvelimeen fsockopen-funktiolla ja tehtailla HTTP-pyynnöt itse. Kolmas mahdollisuus on valmiin kirjaston käyttö, jolloin kyseinen kirjasto täytyy toki ensin aktivoida PHP:n asetuksista. Yleensä valmiiden kirjastojen käyttö säästää vaivaa.

Yksi tehokas kirjasto on cURL. Se osaa muun muassa lähettää POST-dataa ja käsitellä evästeitä; näillä yleensä pääseekin jo pitkälle. Jos palvelimella on openssl-laajennos, cURL tukee myös suojattua HTTPS-protokollaa.

PHP:llä cURLin käyttö on melko suoraviivaista: Ensin cURL alustetaan funktiolla curl_init. Sitten asetetaan tarvittavat asetukset funktiolla curl_setopt. Sivu haetaan funktiolla curl_exec, ja lopuksi cURL suljetaan funktiolla curl_close.

Tässä vinkissä kirjaudutaan cURL-kirjastolla Helmet-palveluun eli pääkaupunkiseudun kirjastojen sivuille ja haetaan sieltä lista omista lainoista.

<?php

// Tämä luokka sisältää muutamia perustoimintoja cURL-yhteyttä varten.
class CurlIstunto {
	// cURL-yhteys
	public $ch;

	// Alkutoimet
	public function __construct() {
		// cURLin alustus
		$this->ch = curl_init();
		$this->setOpt(CURLOPT_USERAGENT, "PHP");

		// Käsketään cURLin palauttaa data muuttujaan eikä tiedostoon.
		$this->setOpt(CURLOPT_RETURNTRANSFER, 1);

		// Käsketään cURLin palauttaa virhetilanteessa false.
		$this->setOpt(CURLOPT_FAILONERROR, 1);

		// Annetaan cURLin käyttää pakkausta.
		$this->setOpt(CURLOPT_ENCODING, "");

		// Laitetaan evästeet käyttöön. Tällä asetuksella evästeitä
		// ei tallenneta levylle ollenkaan; sen sijaan asetuksella
		// CURLOPT_COOKIEJAR voisi myös tallentaa evästeet tiedostoon.
		$this->setOpt(CURLOPT_COOKIEFILE, "");

		// Seuraavilla riveillä saa SSL-sertifikaatin tarkistuksen pois:
		// $this->setOpt(CURLOPT_SSL_VERIFYHOST, 0);
		// $this->setOpt(CURLOPT_SSL_VERIFYPEER, 0);
	}

	// Lopetus
	public function sulje() {
		curl_close($this->ch);
		$this->ch = null;
	}

	// Asetuksen muuttaminen
	public function setOpt($opt, $val) {
		return curl_setopt($this->ch, $opt, $val);
	}

	// Nykyisillä asetuksilla sivun lataaminen
	protected function lataa() {
		return curl_exec($this->ch);
	}

	// GET-pyyntö
	public function get($url, $data = []) {
		$this->setOpt(CURLOPT_POST, 0);
		if ($data) {
			$data = http_build_query($data);
			$q = strpos($url, "?") ? "&" : "?";
			$url = $url . $q . $data;
		}
		$this->setOpt(CURLOPT_URL, $url);
		$this->setOpt(CURLOPT_HTTPHEADER, []);
		return $this->lataa();
	}

	// POST-pyyntö
	public function post($url, $data, $type) {
		$this->setOpt(CURLOPT_POST, 1);
		$this->setOpt(CURLOPT_POSTFIELDS, $data);
		$this->setOpt(CURLOPT_HTTPHEADER, ["Content-Type: {$type}", "Content-Length: ".strlen($data)]);
		$this->setOpt(CURLOPT_URL, $url);
		return $this->lataa();
	}

	// POST-pyyntö lomakedatalla
	public function postForm($url, $data) {
		return $this->post($url, http_build_query($data), "application/x-www-form-urlencoded");
	}

	// POST-pyyntö JSON-datalla
	public function postJson($url, $data) {
		return $this->post($url, json_encode($data), "application/json");
	}
}

class HelmetIstunto extends CurlIstunto {
	// Oma id Helmetin järjestelmässä.
	private $patronId;

	// Kirjautuminen Helmet-tunnuksilla
	public function __construct($kortti, $tunnusluku) {
		parent::__construct();

		// Haetaan kirjautumissivu, niin saadaan istunto aloitettua.
		$html = $this->get("https://luettelo.helmet.fi/iii/cas/login");

		// Luodaan kirjautumislomakkeen data.
		$login_data = ["code" => $kortti, "pin" => $tunnusluku];

		// Luetaan kirjautumissivulta lomakkeeseen tarvittavia tietoja.
		$doc = new DOMDocument();
		@$doc->loadHTML($html);
		$xpath = new DOMXPath($doc);

		// Luetaan lomakkeen osoite.
		$url = $xpath->evaluate("string(//form[@id='fm1']/@action)");
		if (substr($url, 0, 1) !== "/") {
			throw new RuntimeException("Helmet on muuttunut, koodia pitää korjata");
		}
		$url = "https://luettelo.helmet.fi".$url;

		// Luetaan lomakkeen piilokentät.
		foreach ($xpath->query("//input[@type='hidden']") as $input) {
			$login_data[$input->getAttribute("name")] = $input->getAttribute("value");
		}

		// Lähetetään kirjautumistiedot.
		$html = $this->postForm($url, $login_data);
		if (strpos($html, "Log In Successful") === false) {
			throw new RuntimeException("Helmet-kirjautuminen epäonnistui");
		}

		// Tarvitaan muutama hyppy, jotta kirjautuminen siirtyy myös
		// domainista luettelo.helmet.fi domainiin haku.helmet.fi
		$this->setOpt(CURLOPT_FOLLOWLOCATION, 1);
		$html = $this->get("https://haku.helmet.fi/iii/encore/myaccount?lang=fin");
		$this->setOpt(CURLOPT_FOLLOWLOCATION, 0);

		// Haetaan oma asiakastunnus; löytyy sivulta iframen osoitteesta.
		$doc = new DOMDocument();
		@$doc->loadHTML($html);
		$xpath = new DOMXPath($doc);
		$iframe_url = $xpath->evaluate("string(//iframe[@id='accountContentIframe']/@src)");
		if (!preg_match('#/patroninfo.*?/(\d+)/#', $iframe_url, $m)) {
			throw new RuntimeException("Helmet on muuttunut, koodia pitää korjata");
		}
		$this->patronId = $m[1];
	}

	// Omien lainojen hakeminen
	public function lainat() {
		// Osoitteeseen tulee oma id.
		$url = "https://luettelo.helmet.fi/dp/patroninfo*fin/{$this->patronId}/items";
		$html = $this->get($url);

		// Dokumentista puuttuu charset, lisätään se.
		$html = str_replace("</head>", "<meta charset='UTF-8' /></head>", $html);

		// Haetaan taulukosta kirjojen nimet ja tilat.
		$doc = new DOMDocument();
		@$doc->loadHTML($html);
		$xpath = new DOMXPath($doc);
		foreach ($xpath->query("//tr[@class='patFuncEntry']") as $tr) {
			$title = $xpath->evaluate("string(.//*[@class='patFuncTitleMain'])", $tr);
			$status = $xpath->evaluate("string(.//*[@class='patFuncStatus'])", $tr);
			$lainat[] = ["nimi" => trim($title), "tila" => trim($status)];
		}
		return $lainat;
	}
}

Koodia voi käyttää näin:

// Haetaan lainat ja näytetään ne JSON-muodossa.
// Huomio! Tarvitset tietenkin omat tunnukset tähän.
$h = new HelmetIstunto("20000000000000", "0000");
$lainat = $h->lainat();
echo json_encode($lainat, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), "\n";
$h->sulje();

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta