WP Mobile Edition und Subdomains

Nachdem ich letztens darüber geschrieben habe, dass ich nun auf WordPress Mobile Edition umgestiegen bin und dort auch erwähnt habe, dass man die Funktionsweise hinter Subdomains verstecken könne, kam die Frage auf, wie man das denn genau machen könne.
Nachdem meine Variante jedoch als nicht allzu toll bezeichnet wurde, habe ich versprochen, mir das ganze nochmal genauer anzusehen.

Wie das, was ich gleich zeigen werde, in der Praxis aussieht, kann man sich testhalber einmal auf Mobile.WeizenSpr.eu/ ansehen (Update: Das Beispiel wird nicht mehr betrieben.). Bitte denkt dabei jedoch daran, dass das Theme nicht an den Test angepasst wurde - deshalb funktioniert der "Exit the Mobile Edition"-Link nicht. Diese kleine Änderung sollte jedoch jeder hinkriegen, der diesem Tutorial hier folgen kann.

Fangen wir zuerst einmal mit der Veränderung des Plugins an. In der wp-mobile.php gibt es ein paar tiefere Änderungen zu erledigen:
Bisher ist es so, dass das Plugin durch den Parameter ?cf_action=show_mobile erstmal nur einen Wert in einem Cookie setzt und danach die Seite neu lädt. Bei dem Reload wird dann das Cookie gelesen und erkannt, dass die mobile Seite benötigt wird. Wir müssen nun dafür sorgen, dass nicht nur das Cookie gesetzt wird, sondern auch direkt die mobile Seite zurückgegeben wird!

Hierfür gehen wir zuerst einmal in die Funktion cfmobi_check_mobile() und gehen runter bis zur letzten Zeile:

1
return apply_filters('cfmobi_check_mobile', $mobile);

Direkt über dieser fügen wir folgenden Code-Schnipsel ein:

1
2
3
4
5
6
7
8
9
10
if (!empty($_GET['cf_action'])) {
  switch ($_GET['cf_action']) {
    case 'reject_mobile':
      $mobile = false;
      break;
    case 'show_mobile':
      $mobile = true;
      break;
  }
}

Um nun zu verhinden, dass der Redirect stattfindet, gehen wir anschließend in die Funktion cfmobi_request_handler() und kommentieren dort folgenden Codeblock aus:

1
2
3
4
5
6
7
8
9
if ($redirect) {
  if (!empty($_SERVER['HTTP_REFERER'])) {
    $go = $_SERVER['HTTP_REFERER'];
  } else {
    $go = get_bloginfo('home');
  }
  header('Location: '.$go);
  die();
}

Nachdem wir diese Änderungen vorgenommen haben, können wir anfangen, unsere Subdomain entsprechend zu gestalten. Hierfür benötigen wir zuerst einmal eine .htaccess Datei, die bei mir wie folgt aussieht:

1
2
3
4
5
6
RewriteEngine on
Options +FollowSymLinks
RewriteBase /m/
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [L]

Wichtig ist die RewriteBase. Diese gibt an, in welchem Unterverzeichnis eure mobile Fassung aktiv werden soll. Wenn ihr direkt im Hauptverzeichnis arbeiten wollt, dann ändert diese einfach in ein einzelnes Slash / um. 😉

Nun kommt erstmal ein dicker Batzen an Quelltext - den ich danach näher erklären werde. Es handelt sich dabei um den Inhalt der index.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<?php
  //++ CONSTANTS
  $urlBefore = "http://mobile.weizenspr.eu/m";
  $urlAfter  = "https://weizenspr.eu";

  $usrAgent= "mobile.weizenspr.eu/m/";
  //-- CONSTANTS

  //++ FUNCTIONS
  // taken from http://www.addedbytes.com/php/querystring-functions/
  function add_querystring_var($url, $key, $value) {
    $url = preg_replace('/(.*)(?|&)' . $key . '=[^&]+?(&)(.*)/i', '$1$2$4', $url . '&');
    $url = substr($url, 0, -1);
    if (strpos($url, '?') === false) {
      return ($url . '?' . $key . '=' . $value);
    } else {
      return ($url . '&' . $key . '=' . $value);
    }
  }

  function get_Server($name) {
    $result = null;
    if (isset($_SERVER[$name])) {
      $result = $_SERVER[$name];
    }

    return $result;
  }

  function get_is_SSL() {
    return ((get_Server("HTTPS") == 1) ||
            (get_Server("HTTPS") == 'on') ||
            (get_Server("SERVER_PORT") == 443));
  }

  function get_current_URL($appendQueryString = true) {
    $protocol = "http";
    if (get_is_SSL()) {
      $protocol = $protocol . "s";
    }
    $protocol = $protocol . "://";

    $port = "";
    if (!get_is_SSL() && (get_Server("SERVER_PORT") != 80)) {
      $port = "" . get_Server("SERVER_PORT") . "";
    }

    $serverName = get_Server("SERVER_NAME");
    $requestURI = get_Server("REQUEST_URI");

    $result = $protocol . $serverName . $port . $requestURI;
    if ($appendQueryString !== true) {
      $queryString = get_Server("QUERY_STRING");
      $position    = strpos($result, $queryString);
      if ($position !== false) {
        if ($result[$position-1] == "?") {
          $result = substr($result, 0, $position-1);
        }
      }
    }

    return $result;
  }

  function get_real_URL($currentURL, $realURL) {
    $result = get_current_URL(true);
    $result = str_replace($currentURL, $realURL, $result);
    $result = add_querystring_var($result, "cf_action", "show_mobile");

    return $result;
  }

  function get_Cookies($cookieArray) {
    $result = "";

    foreach ($cookieArray as $name=>$value) {
      $result .= urlencode($name) . "=" . urlencode($value) . ";";
    }

    return $result;
  }
  //-- FUNCTIONS

  //++ IMPLEMENTATION
  $mobileURL = get_real_URL($urlBefore, $urlAfter);

  $options = array(
    CURLOPT_AUTOREFERER    => true,
    CURLOPT_COOKIE         => get_Cookies($_COOKIE),
//    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HEADER         => false,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $_POST,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_SSL_VERIFYHOST => 0,
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_USERAGENT      => $usrAgent,
    CURLOPT_VERBOSE        => 1
  );
  $mobileAccess = curl_init($mobileURL);
  curl_setopt_array($mobileAccess, $options);
  $mobileContent = curl_exec($mobileAccess);
  $mobileHeader  = curl_getinfo($mobileAccess);
  curl_close($mobileAccess);

  $textType = "text";
  if (strpos($mobileHeader["content_type"], $textType) !== false) {
    $mobileContent = str_replace($urlAfter, $urlBefore, $mobileContent);
  }

  header("Content-Type: " . $mobileHeader["content_type"]);
  print($mobileContent);
  //-- IMPLEMENTATION
?>

Ich würde sagen, ich fange bei der Erklärung oben an und arbeite mich nach unten. Wie man sehen kann, gibt es mehrere Abschnitte: CONSTANTS, FUNCTIONS und IMPLEMENTATION.

Für den Endanwender wichtig ist lediglich der Abschnitt CONSTANTS. Dort sind alle Werte hinterlegt, die man eventuell ändern muss:

  • Die Variable $urlBefore gibt an, wie der Anfang sämtlicher mobiler URLs aussieht. Wichtig ist, zu wissen, dass am Ende ein 1:1 Mapping zwischen mobiler und normaler URL stattfindet.
  • Die Variable $urlAfter gibt den Anfang der normalen URL an. In meinem Fall werden alle URLs der Form http://mobile.weizenspr.eu/m$1 auf https://weizenspr.eu$1 umgeschrieben.
  • Die Variable $usrAgent gibt an, unter welchem Namen die Seite vom Originalserver geladen wird. Das kann zum Beispiel bei der Fehlersuche interessant sein, wenn man sich durch die Serverlogs wuseln darf.

Im Abschnitt FUNCTIONS werden einige Funktionen definiert, die später in der Programmlogik benötigt werden:

  • Die Funktion add_querystring_var() habe ich mir von hier ausgeliehen. Sie dient dazu, später an die abgefragte URL den Parameter ?cf_action=show_mobile anzufügen.
  • Die Funktion get_current_URL() dient dazu, die aktuelle URL herauszufinden. Es wäre auch möglich gewesen, in der .htaccess dafür zu sorgen, dass die aktuelle URL als GET-Parameter an die index.php gegeben wird - allerdings hätte es dann evtl. Probleme gegeben, wenn die eigentliche Seite ebenfalls Parameter benötigt.
  • Die Funktion get_real_URL() dient dazu, aus den angegebenen Konstanten die eigentliche mobile URL zu erzeugen, wie sie von WP Mobile Edition erwartet wird.
  • Die Funktion get_Cookies() dient abschließend dazu, aus den zur Verfügung stehenden Cookie-Informationen einen Cookie-String für cURL zu generieren.

Nun zur eigentlichen IMPLEMENTATION: So banal es klingen mag, aber im Grunde implementieren wir hier einen Proxy in PHP. Wir holen uns die angeforderte Adresse durch den Querystring der aktuellen Seite, generieren daraus die Adresse der Zielseite, rufen diese mit Hilfe der cURL-Bibliothek ab und geben diese aus. Es gibt lediglich 3 Feinheiten, die wir beachten müssen:

  1. Wir müssen darauf achten, dass wir die Dateien mit dem richtigen Dateityp weiterreichen. Wenn wir z.B. ein Bild vom fernen Server abrufen, dann müssen auch wir die Datei als Bild weiterreichen und nicht etwa als Textdatei.
  2. Weiterhin ist es wichtig, dass wir nicht vergessen, die POST-Informationen, die an den Proxy geschickt werden, auch wirklich an die Ursprungsseite weitergeleitet werden. Ansonsten können wir keine Kommentare schreiben oder anderweitig über Formulare mit der eigentlichen Webseite aggieren.
  3. Zudem müssen wir dafür sorgen, dass wir alle Links auf die eigentliche Originalseite (in $urlAfter) auf die mobile Seite (in $urlBefore) umbiegen. Ansonsten würden wir bei einem Klick auf einen (Artikel-) Link automatisch zur Originalseite geleitet werden. Das wäre jedoch abträglich für die Illusion von einer vollkommen eigenständigen Subdomain.

Eine wichtige Änderung muss unbedingt noch vorgenommen werden. In der Datei wp-comments-post.php von WordPress werden die eintrudelnden Kommentare verarbeitet. Das funktioniert soweit ganz gut, bis auf eine kleine Ausnahme: Eigentlich wird man nach dem Absenden des Kommentars genau dorthin weitergeleitet. Dies geschieht über diesen Aufruf ganz am Ende des Quelltextes:

1
wp_redirect($location);

Diese Weiterleitung funktioniert über den Proxy leider nicht. Ich weiß noch nicht genau weshalb, werde mir das ganze aber in ruhiger Stunde einmal ansehen. Jedenfalls können wir das Problem durch ein Stück eigenen PHP-Code umschiffen. Dafür fügen wir nach besagter Zeile noch folgenden Quelltext ein:

1
2
3
4
5
6
7
8
9
10
  print("<html>");
  print("  <head>");
  print("    <title>Comment saved.</title>");
  print("    <meta http-equiv="refresh" content="0;url=" . $location . "">");
  print("  </head>");
  print("  <body>");
  print("    Your comment has been saved.<br/>");
  print("    You will be redirected immediately: <a href="" . $location . "">LINK</a>");
  print("  </body>");
  print("</html>");

Damit bieten wir dem Proxy (und damit dem mobilen Client) die Möglichkeit, den Redirect selber durchzuführen. 🙂

Die einzige Änderung, die man nun noch vornehmen könnte, wäre die, dass mobile Surfer, die auf der Originalseite auftreffen, auf die mobile Subdomain weitergeleitet werden. Wer sich gemerkt hat, dass es in der Datei wp-mobile.php die Funktion cfmobi_check_mobile() gibt, ist bereits einen großen Schritt näher, die Lösung für dieses Problem zu finden. Ich will euch ja schließlich nicht sämtlichen Spaß nehmen. 😉

Nundann, ich hoffe, diese ausschweifende Beschreibung hat einigen von euch geholfen, ihre mobile Seite noch ein bisschen zu verschönern. Allen, die das ganze absolut nicht interessiert hat, verspreche ich, dass es auch wieder normalere Artikel geben wird. 😀

Und nicht vergessen: Ich hafte nicht für Schäden an Software, Hardware oder für Vermögensschäden, die durch Anwendung dieser Änderungen entstanden sind oder entstehen könnten. 😉

Update:
Gerade sind die ersten Errorlogs eingetrudelt, deswegen habe ich noch ein wenig optimiert: Wichtig ist, dass CURLOPT_FOLLOWLOCATION nicht erlaubt ist, wenn der SafeMode von PHP aktiviert ist. Zudem habe ich die Funktionen get_is_SSL() und get_current_URL() umgeschrieben und habe die Funktion get_Server() eingeführt. Auf meinem Server ist die Variable $_SERVER["HTTPS"] nicht definiert, weshalb bei jedem Aufruf 2 Fehler ausgeworfen wurden.

Update:
Es ist nun möglich, Kommentare über den Proxyclient zu senden. Hierfür ist leider eine kleine Änderung an der WordPress-Datei wp-comments-post.php notwendig.

Subdomainige Grüße, Kenny

8 Kommentare » Schreibe einen Kommentar

  1. Hallo, ich check deinen Artikel nicht. Von welcher index.php (dicker Batzen an Quelltext) sprichst du? Die aus dem /root/ von WordPress? Und was hat dein Verzeichnis /m/ auf sich?

    Also ich habe das Mobile Edition Plugin für WordPress normal installiert. Die PHP in das Plugin-verzeichnis und das Template ins Template-Verzeichnis.

    Wäre cool wenn du mir das bitte genauer erklären könntest. Per Email ginge auch fals das etwas komplizierter wird.

    Vielen lieben dank
    René

    • Aaalso, mit dem Verzeichnis /m/ hat es nicht viel auf sich: Die Domain mobile.weizenspr.eu dient mir bereits als einfache Weiterleitung auf weizenspr.eu/?cf_action=show_mobile

      Die Frage war jedoch, wie man es hinkriegt, die mobile Variante des Blogs komplett unter einer seperaten Domain zu betreiben. Um das präsentieren zu können, habe ich es im Unterordner /m/ eingerichtet gehabt.

      Der dicke Batzen an Quelltext ist die index.php, die im Root-Verzeichnis der Mobil-Domain abgelegt und (oben im Quelltext) konfiguriert werden müsste. Auch die gezeigte .htaccess Datei müsste dort abgelegt werden, damit die URL-Pfade korrekt behandelt werden können.

      Das war es eigentlich schon. 🙂

      • Hallo, also ich glaub ich habs noch viel einfacher gestaltet. Ich hab keine änderungen an den Dateien vorgenommen oder der .htaccess etwas hinzugefügt. Habe einen Ordner /mobile/ erstellt, eine index.php mit einer Weiterleitung zu domain.de?cf_action=show_mobile rein kopiert und fertig.

        Jetzt gibts die Variante das das tragbare Gerät automatisch erkannt wird und auf die Mobile Version weitergeleitet wird oder anhand der Subdomain.

        Klappt alles wunderbar. Thx für den Denkanstoß 🙂

    • Dieser Lösungsansatz ist mir bekannt, aber aufgrund der mir Gott-gegebenen Serverkonfiguration für mich nicht durchführbar/testbar. Shared Hoster verdienen u.a. daran, dass man eben nicht beliebig viele Subdomains hat, sondern diese in Paketen dazukaufen kann/muss, wenn das vorhandene Kontingent nicht ausreicht.

  2. Tip: Du hättest dir die ganze arbeit mit entsprechenden RewriteRule's via .htaccess sparen können. Sind nur ein paar zeilen.

Schreibe einen Kommentar

Um Ihnen beim weiteren Kommentieren auf dieser Webseite die erneute Eingabe Ihrer Daten zu ersparen, wird beim Absenden Ihres Kommentars ein Cookie an Ihren Browser gesendet und von diesem gespeichert. Mit dem Absenden eines Kommentars auf dieser Webseite stimmen Sie der Speicherung und Übertragung dieses Cookies explizit zu.

Pflichtfelder sind mit * markiert.