Merge branch 'testing'

This commit is contained in:
Martin Schütte 2014-06-16 19:05:36 +02:00
commit f2a26967ad
16 changed files with 1110 additions and 377 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
bin
vendor
composer.lock
*.sublime-project
*.sublime-workspace
nbproject/

View file

@ -4,11 +4,15 @@ php:
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
env:
- WP_VERSION=latest WP_MULTISITE=0
- WP_VERSION=3.8
- WP_VERSION=latest
before_script:
- bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION
- fgrep wp_version /tmp/wordpress/wp-includes/version.php
script: phpunit

16
behat.yml Normal file
View file

@ -0,0 +1,16 @@
default:
context:
parameters:
base_url: http://localhost:8080/
role_map:
ender: subscriber
starter: editor
extensions:
Behat\MinkExtension\Extension:
base_url: http://localhost:8080/
goutte: ~
selenium2:
wd_host: http://localhost:8910/wd/hub
browser_name: phantomjs
default_session: selenium2
javascript_session: selenium2

16
composer.json Normal file
View file

@ -0,0 +1,16 @@
{
"minimum-stability": "stable",
"config": {
"bin-dir": "bin",
"vendor-dir": "vendor"
},
"require-dev": {
"phpunit/phpunit": "4.1.*",
"behat/behat": "2.5.3",
"behat/behat": "2.5.3",
"behat/mink": "1.5",
"behat/mink-extension": "*",
"behat/mink-goutte-driver": "*",
"behat/mink-selenium2-driver": "*"
}
}

View file

@ -0,0 +1,67 @@
<?php
use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\TranslatedContextInterface,
Behat\Behat\Context\BehatContext,
Behat\Behat\Context\Step\When,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
require "WordPressContext.php";
use \WordPress\Mink\Context as WP_Context;
//
// Require 3rd-party libraries here:
//
// require_once 'PHPUnit/Autoload.php';
// require_once 'PHPUnit/Framework/Assert/Functions.php';
//
/**
* Features context.
*/
class FeatureContext extends WP_Context\WordPress_Context
{
/**
* @Given /^I am logged in as "([^"]*)" with "([^"]*)"$/
*/
public function iAmLoggedInAsWith($username, $password) {
// Works out of the box (with a base_url of course)
// And makes sure the current user is logged out first!
$this->login($username, $password);
}
/**
* @When /^I write a post with title "([^"]*)" and content "([^"]*)"$/
*/
public function iWriteAPostWithTitleAndContent($post_title, $content)
{
$this->fill_in_post('post', $post_title, 'publish', $content);
}
/**
* @Given /^the plugin "([^"]*)" is "([^"]*)"$/
*/
public function thePluginIs($plugin, $state)
{
if ($state == "active") {
$action = "activate";
} else {
$action = "deactivate";
}
shell_exec(escapeshellcmd("wp plugin $action $plugin"));
}
/**
* @When /^I search for "([^"]*)"$/
*/
public function iSearchFor($term)
{
return array(
new When("I fill in \"s\" with \"$term\""),
new When("I press \"searchsubmit\""),
);
}
}

View file

@ -0,0 +1,248 @@
<?php
# copied and adapted from
# https://github.com/maartenJacobs/WordPress-Behat-Context/blob/master/WordPressContext.php
namespace WordPress\Mink\Context;
use Behat\MinkExtension\Context\MinkContext as BehatContext;
class WordPress_Context extends BehatContext {
protected $base_url;
protected $role_map;
protected $session;
/**
* Initializes context.
* Every scenario gets it's own context object.
*
* @param array $parameters context parameters (set them up through behat.yml)
*/
public function __construct(array $params) {
$this->base_url = $params['base_url'];
$this->role_map = $params['role_map'];
}
/**
* Given a list of usernames (user_login field), checks for every username
* if they exist. Returns a list of the users that do not exist.
*
* @param array $users
* @return array
* @author Maarten Jacobs
**/
protected function check_users_exist(array $users) {
$session = $this->getSession();
// Check if the users exist, saving the inexistent users
$inexistent_users = array();
$this->visit( 'wp-admin/users.php' );
$current_page = $session->getPage();
foreach ($users as $username) {
if (!$current_page->hasContent($username)) {
$inexistent_users[] = $username;
}
}
return $inexistent_users;
}
/**
* Creates a user for every username given (user_login field).
* The inner values can also maps of the following type:
* array(
* 'username' =>
* 'password' => (default: pass)
* 'email' => (default: username@test.dev)
* 'role' => (default: checks rolemap, or 'subscriber')
* )
*
* @param array $users
* @author Maarten Jacobs
**/
protected function create_users(array $users) {
$session = $this->getSession();
foreach ($users as $username) {
if (is_array($username)) {
$name = $username['username'];
$password = array_key_exists('password', $username) ? $username['password'] : 'pass';
$email = array_key_exists('email', $username) ? $username['email'] : str_replace(' ', '_', $name) . '@test.dev';
} else {
$name = $username;
$password = 'pass';
$email = str_replace(' ', '_', $name) . '@test.dev';
}
$this->visit( 'wp-admin/user-new.php' );
$current_page = $session->getPage();
// Fill in the form
$current_page->findField('user_login')->setValue($name);
$current_page->findField('email')->setValue($email);
$current_page->findField('pass1')->setValue($password);
$current_page->findField('pass2')->setValue($password);
// Set role
$role = ucfirst( strtolower( $this->role_map[$name] ) );
$current_page->findField('role')->selectOption($role);
// Submit form
$current_page->findButton('Add New User')->click();
}
}
/**
* Fills in the form of a generic post.
* Given the status, will either publish or save as draft.
*
* @param string $post_type
* @param string $post_title
* @param string $status Either 'draft' or anything else for 'publish'
* @author Maarten Jacobs
**/
protected function fill_in_post($post_type, $post_title, $status = 'publish', $content = '<p>Testing all the things. All the time.</p>') {
// The post type, if not post, will be appended.
// Rather than a separate page per type, this is how WP works with forms for separate post types.
$uri_suffix = $post_type !== 'post' ? '?post_type=' . $post_type : '';
$this->visit( 'wp-admin/post-new.php' . $uri_suffix );
$session = $this->session = $this->getSession();
$current_page = $session->getPage();
// Fill in the title
$current_page->findField( 'post_title' )->setValue( $post_title );
// Fill in some nonsencical data for the body
// clickLink and setValue seem to be failing for TinyMCE (same for Cucumber unfortunately)
$session->executeScript( 'jQuery( "#content-html" ).click()' );
$session->executeScript( 'jQuery( "#content" ).val( "' . $content . '" )' );
// Click the appropriate button depending on the given status
$state_button = 'Save Draft';
switch ($status) {
case 'draft':
// We're good.
break;
case 'publish':
default:
// Save as draft first
$current_page->findButton($state_button)->click();
$state_button = 'Publish';
break;
}
$current_page->findButton($state_button)->click();
// go to view of new post
$session->getPage()->clickLink("View $post_type");
}
/**
* Makes sure the current user is logged out, and then logs in with
* the given username and password.
*
* @param string $username
* @param string $password
* @author Maarten Jacobs
**/
protected function login($username, $password = 'pass') {
$session = $this->session = $this->getSession();
$current_page = $session->getPage();
// Check if logged in as that user
$this->visit( 'wp-admin' );
if ($current_page->hasContent( "Howdy, {$username}" )) {
// We're already logged in as this user.
// Double-check
$this->assertPageContainsText('Dashboard');
return true;
}
// Logout
$this->visit( 'wp-login.php?action=logout' );
if ($session->getPage()->hasLink('log out')) {
$session->getPage()->clickLink('log out');
$current_page = $session->getPage();
}
// And login
$current_page->fillField('user_login', $username);
$current_page->fillField('user_pass', $password);
$current_page->findButton('wp-submit')->click();
// Assert that we are on the dashboard
$this->assertPageContainsText('Dashboard');
}
/**
* Given the current page is post list page, we enter the title in the searchbox
* and search for that post.
*
* @param string $post_title The title of the post as it would appear in the WP backend
* @param boolean $do_assert If set to anything but false, will assert for the existence of the post title after the search
* @return void
* @author Maarten Jacobs
**/
protected function searchForPost( $post_title, $do_assert = FALSE ) {
$current_page = $this->getSession()->getPage();
// Search for the post
$search_field = $current_page->findField( 'post-search-input' ); // Searching on #id
// When there is no content, then the searchbox is not shown
// So we skip search in that case
if ($search_field) {
$search_field->setValue( $post_title );
$current_page->findField( 'Search Posts' ) // Searching on value
->click();
}
// We don't stop tests even if the searchbox does not exist.
// That would prevent the dev from knowing what the hell's going on.
// Can I assert all the things?
if ( $do_assert ) {
$this->assertPageContainsText($post_title);
}
}
/**
* @Given /^I trash the "([^"]*)" titled "([^"]*)"$/
*/
public function iTrashThePostTitled( $post_type, $post_title ) {
$session = $this->session = $this->getSession();
// Visit the posts page
$uri_suffix = $post_type !== 'post' ? '?post_type=' . $post_type : '';
$postlist_uri = 'wp-admin/edit.php' . $uri_suffix;
$this->visit( $postlist_uri );
$current_page = $session->getPage();
// Check if the post with that title is on the current page
if (!$current_page->hasContent( $post_title )) {
// If not, search for the post
$this->searchForPost( $post_title );
}
$this->assertPageContainsText($post_title);
// Select the post in the checkbox column
// This is tricky: the checkbox has a non-unique name (of course, that's the way to do it)
// So we need to check the box in a different way
// The easiest: jQuery
$session->executeScript( "jQuery( \"tr:contains('$post_title') :checkbox\" ).click()" );
// Trash it
// - Select the 'Move to Trash' option
$current_page->selectFieldOption( 'action', 'Move to Trash' );
// - Click to Apply
$current_page->findButton( 'doaction' )->click();
// Check if the post is no longer visible on the posts page
$this->visit( $postlist_uri );
$this->assertPageNotContainsText( $post_title );
// Make a search, because it could be on another page
$this->searchForPost( $post_title );
$this->assertPageNotContainsText( $post_title );
}
}

View file

@ -0,0 +1,21 @@
Feature: Use Shortcodes
In order to use my Plugin
As a website author
I need to write posts with shortcodes
Background:
Given I am logged in as "admin" with "vagrant"
Scenario: Without the plugin
Given the plugin "freifunkmeta" is "inactive"
When I write a post with title "test" and content "[ff_contact]"
#Then print current URL
Then I should see "ff_contact"
Scenario: With the plugin
Given the plugin "freifunkmeta" is "active"
When I write a post with title "test" and content "[ff_contact]"
#Then print current URL
Then I should see "Twitter" in the ".ff_contact" element
And I should not see "ff_contact"

View file

@ -0,0 +1,10 @@
Feature: Search
In order to find older articles
As a website user
I need to be able to search for a word
Scenario: Searching for a post
Given I am on the homepage
When I search for "Welcome"
Then I should see "Hello world!"
And I should see "Welcome to WordPress. This is your first post."

View file

@ -3,32 +3,47 @@
Plugin Name: Freifunk Metadata Shortcodes
Plugin URI: http://mschuette.name/
Description: Defines shortcodes to display Freifunk metadata
Version: 0.3
Version: 0.4dev
Author: Martin Schuette
Author URI: http://mschuette.name/
*/
define('FF_META_DEFAULT_CACHETIME', 15);
define('FF_META_DEFAULT_DIR', 'https://raw.githubusercontent.com/freifunk/directory.api.freifunk.net/master/directory.json');
define('FF_META_DEFAULT_CITY', 'hamburg');
define( 'FF_META_DEFAULT_CACHETIME', 15 );
define( 'FF_META_DEFAULT_DIR', 'https://raw.githubusercontent.com/freifunk/directory.api.freifunk.net/master/directory.json' );
define( 'FF_META_DEFAULT_CITY', 'hamburg' );
/**
* class to fetch and cache data from external URLs
* returns either an array from decoded JSON data, or WP_Error
*/
class FF_Meta_Externaldata
{
public function get($url) {
/* gets metadata from URL, handles caching */
$cachekey = 'ff_metadata_'.hash('crc32', $url);
$cachetime = get_option('ff_meta_cachetime', FF_META_DEFAULT_CACHETIME) * MINUTE_IN_SECONDS;
public function get( $url ) {
//error_log( "FF_Meta_Externaldata::get( $url )" );
/* gets metadata from URL, handles caching,
* hashed because cache keys should be <= 40 chars */
$cachekey = 'ff_metadata_'.hash( 'crc32', $url );
$cachetime = get_option(
'ff_meta_cachetime', FF_META_DEFAULT_CACHETIME
) * MINUTE_IN_SECONDS;
// Caching
if ( false === ( $data = get_transient($cachekey) ) ) {
$http_response = wp_remote_get($url);
$json = wp_remote_retrieve_body($http_response);
$data = json_decode ($json, $assoc = true);
if ( WP_DEBUG || ( false === ( $data = get_transient( $cachekey ) ) ) ) {
$args = array( 'sslverify' => false );
$http_response = wp_remote_get( $url, $args );
if ( is_wp_error( $http_response ) ) {
$error_msg = sprintf(
'Unable to retrieve URL %s, error: %s',
$url, $http_response->get_error_message()
);
error_log( $error_msg, 4 );
return $http_response;
} else {
$json = wp_remote_retrieve_body( $http_response );
$data = json_decode( $json, $assoc = true );
set_transient( $cachekey, $data, $cachetime );
}
}
return $data;
}
}
@ -39,20 +54,40 @@ class FF_Meta_Externaldata
class FF_Directory
{
private $directory;
private $ed;
function __construct() {
$ed = new FF_Meta_Externaldata();
$this->directory = $ed->get(FF_META_DEFAULT_DIR);
}
function get_url_by_city($city) {
$val = $this->directory[$city];
if (empty($val)) {
return false;
function __construct( $ext_data_service = null ) {
if ( is_null( $ext_data_service ) ) {
$this->ed = new FF_Meta_Externaldata();
} else {
return $val;
$this->ed = $ext_data_service;
}
$data = $this->ed->get( FF_META_DEFAULT_DIR );
if ( is_wp_error( $data ) ) {
$this->directory = array();
} else {
$this->directory = $data;
}
}
function get_url_by_city( $city ) {
if ( array_key_exists( $city, $this->directory ) ) {
return $this->directory[$city];
} else {
return false;
}
}
// get one big array of all known community data
function get_all_data() {
$all_locs = array();
foreach ( $this->directory as $tmp_city => $url ) {
$tmp_meta = $this->ed->get( $url );
if ( ! is_wp_error( $tmp_meta ) ) {
$all_locs[$tmp_city] = $tmp_meta;
}
}
return $all_locs;
}
}
@ -71,38 +106,52 @@ class FF_Community
/**
* Default constructor from metadata
*/
function __construct($metadata) {
function __construct( $metadata ) {
$loc = $metadata['location'];
$this->name = (isset($loc['address']) && isset($loc['address']['Name'])) ? $loc['address']['Name'] : '';
$this->street = (isset($loc['address']) && isset($loc['address']['Street'])) ? $loc['address']['Street'] : '';
$this->zip = (isset($loc['address']) && isset($loc['address']['Zipcode'])) ? $loc['address']['Zipcode'] : '';
$this->city = isset($loc['city']) ? $loc['city'] : '';
$this->lon = isset($loc['lon']) ? $loc['lon'] : '';
$this->lat = isset($loc['lat']) ? $loc['lat'] : '';
$this->name = ( isset( $loc['address'] ) && isset( $loc['address']['Name'] ) )
? $loc['address']['Name'] : '';
$this->street = ( isset( $loc['address'] ) && isset( $loc['address']['Street'] ) )
? $loc['address']['Street'] : '';
$this->zip = ( isset( $loc['address'] ) && isset( $loc['address']['Zipcode'] ) )
? $loc['address']['Zipcode'] : '';
$this->city = isset( $loc['city'] ) ? $loc['city'] : '';
$this->lon = isset( $loc['lon'] ) ? $loc['lon'] : '';
$this->lat = isset( $loc['lat'] ) ? $loc['lat'] : '';
}
/**
* Alternative constructor from city name
*/
static function make_from_city($city) {
// TODO: test
if (false === ($url = $this->dir->get_url_by_city($city))) {
return "<!-- FF Meta Error: cannot get directory.json, or no URL for '$city' -->\n";
static function make_from_city( $city, $ext_data_service = null ) {
if ( is_null( $ext_data_service ) ) {
$ed = new FF_Meta_Externaldata();
} else {
$ed = $ext_data_service;
}
if (false === ($metadata = FF_Meta_Externaldata::get($url))) {
$directory = new FF_Directory( $ed );
if ( false === ( $url = $directory->get_url_by_city( $city ) ) ) {
return '<!-- FF Meta Error: cannot get directory.json, '.
" or no URL for '$city' -->\n";
}
if ( false === ( $metadata = $ed->get( $url ) ) ) {
return "<!-- FF Meta Error: cannot get metadata from $url -->\n";
}
return new FF_Community($metadata);
return new FF_Community( $metadata );
}
function format_address() {
if (empty($this->name) || empty($this->street) || empty($this->zip)) {
if ( empty( $this->name ) || empty( $this->street ) || empty( $this->zip ) ) {
return '';
}
// TODO: style address + map as single box
// TODO: once it is "ready" package openlayers.js into the plugin (cf. http://docs.openlayers.org/library/deploying.html)
// TODO: handle missing values (i.e. only name & city)
return '<p>' . sprintf('%s<br/>%s<br/>%s %s', $this->name, $this->street, $this->zip, $this->city) . '</p>';
// TODO: once it is "ready" package openlayers.js into the plugin
// ( cf. http://docs.openlayers.org/library/deploying.html )
// TODO: handle missing values ( i.e. only name & city )
return sprintf(
'<p>%s<br/>%s<br/>%s %s</p>',
$this->name, $this->street, $this->zip, $this->city
);
}
}
@ -112,204 +161,271 @@ class FF_Community
class FF_Meta
{
private $dir;
private $ed;
function __construct() {
$this->dir = new FF_Directory();
function reinit_external_data_service( $ext_data_service = null ) {
if ( is_null( $ext_data_service ) ) {
$this->ed = new FF_Meta_Externaldata();
} else {
$this->ed = $ext_data_service;
}
$this->dir = new FF_Directory( $this->ed );
}
function __construct( $ext_data_service = null ) {
if ( is_null( $ext_data_service ) ) {
$this->ed = new FF_Meta_Externaldata();
} else {
$this->ed = $ext_data_service;
}
$this->dir = new FF_Directory( $this->ed );
}
function register_stuff() {
if ( ! shortcode_exists('ff_state') ) {
add_shortcode('ff_state', array($this, 'shortcode_handler'));
if ( ! shortcode_exists( 'ff_state' ) ) {
add_shortcode( 'ff_state', array( $this, 'shortcode_handler' ) );
}
if ( ! shortcode_exists('ff_services') ) {
add_shortcode('ff_services', array($this, 'shortcode_handler'));
if ( ! shortcode_exists( 'ff_services' ) ) {
add_shortcode( 'ff_services', array( $this, 'shortcode_handler' ) );
}
if ( ! shortcode_exists('ff_contact') ) {
add_shortcode('ff_contact', array($this, 'shortcode_handler'));
if ( ! shortcode_exists( 'ff_contact' ) ) {
add_shortcode( 'ff_contact', array( $this, 'shortcode_handler' ) );
}
if ( ! shortcode_exists('ff_location') ) {
add_shortcode('ff_location', array($this, 'shortcode_handler'));
if ( ! shortcode_exists( 'ff_location' ) ) {
add_shortcode( 'ff_location', array( $this, 'shortcode_handler' ) );
}
if ( ! shortcode_exists( 'ff_list' ) ) {
add_shortcode( 'ff_list', array( $this, 'shortcode_handler' ) );
}
add_action('admin_menu', array($this, 'admin_menu'));
add_action('admin_init', array($this, 'admin_init'));
register_uninstall_hook( __FILE__, array('ff_meta', 'uninstall_hook'));
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
add_action( 'admin_init', array( $this, 'admin_init' ) );
register_uninstall_hook( __FILE__, array( 'ff_meta', 'uninstall_hook' ) );
}
function output_ff_state($citydata) {
$state = $citydata['state'];
return sprintf('%s', $state['nodes']);
}
function aux_get_all_locations() {
// gather all location data
if (false === ( $json_locs = get_transient("FF_metadata_json_locs") )) {
private function aux_get_all_locations_json() {
if ( WP_DEBUG || ( false === ( $json_locs = get_transient( 'FF_metadata_json_locs' ) ) ) ) {
$all_locs = array();
$arr_select = array('lat' => 1, 'lon' => 1);
foreach ($this->dir as $tmp_city => $url) {
try {
$tmp_meta = FF_Meta_Externaldata::get($url);
if (!empty($tmp_meta['location'])) {
$tmp_loc = array_intersect_key($tmp_meta['location'], $arr_select);
$all_locs[$tmp_city] = $tmp_loc;
}
} catch (Exception $e) {
// pass
$comm_list = $this->dir->get_all_data();
foreach ( $comm_list as $entry ) {
if ( isset( $entry['location'] )
&& isset( $entry['location']['lat'] )
&& isset( $entry['location']['lon'] )
) {
$all_locs[$entry['location']['city']] = array(
'lat' => $entry['location']['lat'],
'lon' => $entry['location']['lon'],
);
}
}
$json_locs = json_encode($all_locs);
$cachetime = get_option('FF_meta_cachetime', FF_META_DEFAULT_CACHETIME) * MINUTE_IN_SECONDS;
set_transient("FF_metadata_json_locs", $json_locs, $cachetime);
$json_locs = json_encode( $all_locs );
$cachetime = get_option( 'FF_meta_cachetime', FF_META_DEFAULT_CACHETIME ) * MINUTE_IN_SECONDS;
set_transient( 'FF_metadata_json_locs', $json_locs, $cachetime );
}
return $json_locs;
}
function output_ff_location($citydata) {
function output_ff_state( $citydata ) {
if ( isset( $citydata['state'] ) && isset( $citydata['state']['nodes'] ) ) {
return sprintf( '%s', $citydata['state']['nodes'] );
} else {
return '';
}
}
function output_ff_location( $citydata ) {
// normal per-city code
$loc = new FF_Community($citydata);
$loc = new FF_Community( $citydata );
$outstr = $loc->format_address();
$json_locs = $this->aux_get_all_locations();
$json_locs = $this->aux_get_all_locations_json();
if (!empty($loc_name) && !empty($loc_name)) {
$icon_url = plugin_dir_url(__FILE__) . "freifunk_marker.png";
if ( ! empty( $loc->name ) && ! empty( $loc->name ) ) {
$icon_url = plugin_dir_url( __FILE__ ) . 'freifunk_marker.png';
$loccity = $loc->city;
$outstr .= <<<EOT
<div id="mapdiv_$loc_city" style="width: 75%; height: 15em;"></div>
<div id="mapdiv_${loccity}" style="width: 75%; height: 15em;"></div>
<style type="text/css"> <!--
/* There seems to be a bug in OpenLayers' style.css (?). Original bottom:4.5em is far too high. */
/* There seems to be a bug in OpenLayers' style.css ( ? ).
* Original bottom:4.5em is far too high. */
#OpenLayers_Control_Attribution_7 { bottom: 3px; }
--></style>
<script src="http://www.openlayers.org/api/OpenLayers.js"></script>
<script>
map = new OpenLayers.Map("mapdiv_$loc->city");
map.addLayer(new OpenLayers.Layer.OSM());
map = new OpenLayers.Map( "mapdiv_${loccity}" );
map.addLayer( new OpenLayers.Layer.OSM() );
var lonLat = new OpenLayers.LonLat( $loc->lon, $loc->lat )
.transform(
new OpenLayers.Projection("EPSG:4326"), // transform from WGS 1984
new OpenLayers.Projection( "EPSG:4326" ), // transform from WGS 1984
map.getProjectionObject() // to Spherical Mercator Projection
);
var markers = new OpenLayers.Layer.Markers( "Markers" );
map.addLayer(markers);
map.addLayer( markers );
markers.addMarker(new OpenLayers.Marker(lonLat));
markers.addMarker( new OpenLayers.Marker( lonLat ) );
var size = new OpenLayers.Size(20,16);
var offset = new OpenLayers.Pixel(0, -(size.h/2));
var icon = new OpenLayers.Icon('$icon_url',size,offset);
var size = new OpenLayers.Size( 20,16 );
var offset = new OpenLayers.Pixel( 0, -( size.h/2 ) );
var icon = new OpenLayers.Icon( '$icon_url',size,offset );
var ff_loc = $json_locs;
delete ff_loc["$city"];
for (key in ff_loc) {
markers.addMarker(new OpenLayers.Marker(
delete ff_loc["$loccity"];
for ( key in ff_loc ) {
markers.addMarker( new OpenLayers.Marker(
new OpenLayers.LonLat( ff_loc[key]['lon'], ff_loc[key]['lat'] )
.transform(new OpenLayers.Projection("EPSG:4326"),map.getProjectionObject()),
.transform( new OpenLayers.Projection( "EPSG:4326" ),map.getProjectionObject() ),
icon.clone()
));
) );
}
var zoom=12;
map.setCenter (lonLat, zoom);
map.setCenter ( lonLat, zoom );
</script>
EOT;
}
return $outstr;
}
function output_ff_services($citydata) {
$outstr = '<ul>';
if (isset($citydata['services'])) {
$services = $citydata['services'];
foreach ($services as $service) {
$outstr .= sprintf('<li>%s (%s): <a href="%s">%s</a></li>', $service['serviceName'], $service['serviceDescription'], $service['internalUri'], $service['internalUri']);
function output_ff_services( $citydata ) {
if ( ! isset( $citydata['services'] ) ) {
return '';
}
$services = $citydata['services'];
$outstr = '<ul>';
foreach ( $services as $service ) {
$outstr .= sprintf(
'<li>%s (%s): <a href="%s">%s</a></li>',
$service['serviceName'], $service['serviceDescription'],
$service['internalUri'], $service['internalUri']
);
}
$outstr .= '</ul>';
return $outstr;
}
function output_ff_contact($citydata) {
$outstr = '<p>';
function output_ff_contact( $citydata ) {
if ( ! isset( $citydata['contact'] ) ) {
return '';
}
$contact = $citydata['contact'];
// Output -- rather ugly but the data is not uniform, some fields are URIs, some are usernames, ...
if (!empty($contact['email'])) {
$outstr .= sprintf("E-Mail: <a href=\"mailto:%s\">%s</a><br />\n", $contact['email'], $contact['email']);
$outstr = '<p>';
// Output -- rather ugly but the data is not uniform,
// some fields are URIs, some are usernames, ...
if ( ! empty( $contact['email'] ) ) {
$outstr .= sprintf(
'E-Mail: <a href=\"mailto:%s\">%s</a><br />',
$contact['email'], $contact['email']
);
}
if (!empty($contact['ml'])) {
$outstr .= sprintf("Mailingliste: <a href=\"mailto:%s\">%s</a><br />\n", $contact['ml'], $contact['ml']);
if ( ! empty( $contact['ml'] ) ) {
$outstr .= sprintf(
'Mailingliste: <a href=\"mailto:%s\">%s</a><br />',
$contact['ml'], $contact['ml']
);
}
if (!empty($contact['irc'])) {
$outstr .= sprintf("IRC: <a href=\"%s\">%s</a><br />\n", $contact['irc'], $contact['irc']);
if ( ! empty( $contact['irc'] ) ) {
$outstr .= sprintf(
'IRC: <a href=\"%s\">%s</a><br />',
$contact['irc'], $contact['irc']
);
}
if (!empty($contact['twitter'])) {
if ( ! empty( $contact['twitter'] ) ) {
// catch username instead of URI
if ($contact['twitter'][0] === "@") {
$twitter_url = 'http://twitter.com/' . ltrim($contact['twitter'], "@");
if ( $contact['twitter'][0] === '@' ) {
$twitter_url = 'http://twitter.com/' . ltrim( $contact['twitter'], '@' );
$twitter_handle = $contact['twitter'];
} else {
$twitter_url = $contact['twitter'];
$twitter_handle = '@' . substr($contact['twitter'], strrpos($contact['twitter'], '/') + 1);
$twitter_handle = '@' . substr(
$contact['twitter'], strrpos( $contact['twitter'], '/' ) + 1
);
}
$outstr .= sprintf("Twitter: <a href=\"%s\">%s</a><br />\n", $twitter_url, $twitter_handle);
$outstr .= sprintf(
'Twitter: <a href=\"%s\">%s</a><br />',
$twitter_url, $twitter_handle
);
}
if (!empty($contact['facebook'])) {
$outstr .= sprintf("Facebook: <a href=\"%s\">%s</a><br />\n", $contact['facebook'], $contact['facebook']);
if ( ! empty( $contact['facebook'] ) ) {
$outstr .= sprintf(
'Facebook: <a href=\"%s\">%s</a><br />',
$contact['facebook'], $contact['facebook']
);
}
if (!empty($contact['googleplus'])) {
$outstr .= sprintf("G+: <a href=\"%s\">%s</a><br />\n", $contact['googleplus'], $contact['googleplus']);
if ( ! empty( $contact['googleplus'] ) ) {
$outstr .= sprintf(
'G+: <a href=\"%s\">%s</a><br />',
$contact['googleplus'], $contact['googleplus']
);
}
if (!empty($contact['jabber'])) {
$outstr .= sprintf("XMPP: <a href=\"xmpp:%s\">%s</a><br />\n", $contact['jabber'], $contact['jabber']);
if ( ! empty( $contact['jabber'] ) ) {
$outstr .= sprintf(
'XMPP: <a href=\"xmpp:%s\">%s</a><br />',
$contact['jabber'], $contact['jabber']
);
}
$outstr .= '</p>';
return $outstr;
}
function output_ff_list() {
return 'here be some ff_list';
$comm_list = $this->dir->get_all_data();
$outstr = '<table>';
$outstr .= '<tr><th>Name</th><th>Stadt</th><th>Knoten</th></tr>';
foreach ( $comm_list as $handle => $entry ) {
$outstr .= sprintf(
'<tr><td><a href="%s">%s</a></td><td>%s</td><td>%s</td></tr>',
esc_url( $entry['url'] ),
isset( $entry['name'] ) ? esc_html( $entry['name'] ) : esc_html($handle),
isset( $entry['location']['city'] ) ? esc_html( $entry['location']['city'] ) : 'n/a',
isset( $entry['state']['nodes'] ) ? esc_html( $entry['state']['nodes'] ) : 'n/a'
);
}
$outstr .= '</table>';
return $outstr;
}
function shortcode_handler($atts, $content, $shortcode) {
function shortcode_handler( $atts, $content, $shortcode ) {
// $atts[0] holds the city name, if given
if (empty($atts[0])) {
$city = get_option('FF_meta_city', FF_META_DEFAULT_CITY);
if ( empty( $atts[0] ) ) {
$city = get_option( 'FF_meta_city', FF_META_DEFAULT_CITY );
} else {
$city = $atts[0];
}
if (false === ($cityurl = $this->dir->get_url_by_city($city))) {
if ( false === ( $cityurl = $this->dir->get_url_by_city( $city ) ) ) {
return "<!-- FF Meta Error: cannot get directory.json, or no URL for '$city' -->\n";
}
if (false === ($metadata = FF_Meta_Externaldata::get($cityurl))) {
$ed = new FF_Meta_Externaldata();
if ( false === ( $metadata = $this->ed->get( $cityurl ) ) ) {
return "<!-- FF Meta Error: cannot get metadata from $cityurl -->\n";
}
$outstr = "<div class=\"ff $shortcode\">";
switch ($shortcode) {
switch ( $shortcode ) {
case 'ff_state':
$outstr .= $this->output_ff_state($metadata);
$outstr .= $this->output_ff_state( $metadata );
break;
case 'ff_location':
$outstr .= $this->output_ff_location($metadata);
$outstr .= $this->output_ff_location( $metadata );
break;
case 'ff_services':
$outstr .= $this->output_ff_services($metadata);
$outstr .= $this->output_ff_services( $metadata );
break;
case 'ff_contact':
$outstr .= $this->output_ff_contact($metadata);
$outstr .= $this->output_ff_contact( $metadata );
break;
case 'ff_list':
$outstr .= $this->output_ff_list();
break;
default:
$outstr .= "";
$outstr .= '';
break;
}
$outstr .= "</div>";
$outstr .= '</div>';
return $outstr;
}
@ -320,7 +436,7 @@ EOT;
'FF Meta', // menu title
'manage_options', // req'd capability
'ff_meta_plugin', // menu slug
array ('FF_meta', 'options_page') // callback function
array( 'FF_meta', 'options_page' ) // callback function
);
}
@ -336,52 +452,60 @@ EOT;
add_settings_section(
'ff_meta_section-one', // ID
'Section One', // Title
array ('FF_Meta', 'section_one_callback'), // callback to fill
array( 'FF_Meta', 'section_one_callback' ), // callback to fill
'ff_meta_plugin' // page to display on
);
add_settings_field(
'ff_meta_city', // ID
'Default community', // Title
array ('FF_Meta', 'city_callback'), // callback to fill field
array( 'FF_Meta', 'city_callback' ), // callback to fill field
'ff_meta_plugin', // menu page=slug to display field on
'ff_meta_section-one', // section to display the field in
array('label_for' => 'ff_meta_city_id') // ID of input element
array( 'label_for' => 'ff_meta_city_id' ) // ID of input element
);
add_settings_field(
'ff_meta_cachetime', // ID
'Cache time', // Title
array ('FF_Meta', 'cachetime_callback'), // callback to fill field
array( 'FF_Meta', 'cachetime_callback' ), // callback to fill field
'ff_meta_plugin', // menu page=slug to display field on
'ff_meta_section-one', // section to display the field in
array('label_for' => 'ff_meta_cachetime_id') // ID of input element
array( 'label_for' => 'ff_meta_cachetime_id' ) // ID of input element
);
}
function section_one_callback() {
echo 'This Plugin provides shortcodes to display information from the Freifunk meta.json.';
echo 'This Plugin provides shortcodes to display information'
.' from the Freifunk meta.json.';
}
function cachetime_callback() {
$time = get_option( 'ff_meta_cachetime', FF_META_DEFAULT_CACHETIME );
echo "<input type='number' name='ff_meta_cachetime' id='ff_meta_cachetime_id' class='small-text code' value='$time' /> minutes"
."<p class='description'>Data from external URLs is cached for this number of minutes.</p>";
echo '<input type="number" name="ff_meta_cachetime" '
.'id="ff_meta_cachetime_id" class="small-text code" value="'
. esc_attr( $time ) . ' /> minutes'
.'<p class="description">Data from external URLs is cached'
.' for this number of minutes.</p>';
}
function city_callback() {
if (false === ($directory = FF_Meta_Externaldata::get ( FF_META_DEFAULT_DIR ))) {
$ed = new FF_Meta_Externaldata();
if ( false === ( $directory = $this->ed->get( FF_META_DEFAULT_DIR ) ) ) {
// TODO: error handling
return;
}
$default_city = get_option( 'ff_meta_city', FF_META_DEFAULT_CITY );
echo "<select name='ff_meta_city' id='ff_meta_city_id' size='1'>";
foreach (array_keys($directory) as $city) {
$prettycity = ucwords(str_replace(array('_', '-'), ' ', $city));
foreach ( array_keys( $directory ) as $city ) {
$prettycity = ucwords( str_replace( array( '_', '-' ), ' ', $city ) );
$selected = selected( $default_city, $city );
echo "<option value='$city' $selected>$prettycity</option>";
printf(
'<option value="%s" %s>%s</option>',
esc_attr( $city ), $selected, esc_str( $prettycity )
);
}
echo "</select>";
echo "<p class='description'>This is the default city parameter.</p>";
echo '</select>';
echo '<p class="description">This is the default city parameter.</p>';
}
function options_page() {
@ -404,5 +528,6 @@ EOT;
}
$ffmeta = new FF_Meta;
$ffmeta = new FF_Meta();
$ffmeta->register_stuff();
$GLOBALS['wp-plugin-ffmeta'] = $ffmeta;

View file

@ -12,3 +12,4 @@ tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
require $_tests_dir . '/includes/bootstrap.php';
require 'mock_ext_dataservice.php';

View file

@ -0,0 +1,4 @@
{
"hamburg" : "http://example.org/ffhh.json",
"ffm" : "http://example.org/ffffm.json"
}

View file

@ -0,0 +1,22 @@
<?php
/*
* Mock for FF_Meta_Externaldata
* in order to run all unit tests offline with predefined content
*/
class MockDataService {
function get($url) {
// translate file "http://example.org/%s.json" to "./example_%s.json"
$url_filename = basename(parse_url($url, PHP_URL_PATH));
$local_filename = __DIR__.'/example_'.$url_filename;
if (file_exists($local_filename)) {
$json = file_get_contents($local_filename);
$stubdata = json_decode($json, $assoc = true);
//error_log("MockDataService: fetch $url from $local_filename", 4);
return $stubdata;
} else {
//error_log("MockDataService: cannot fetch $url", 4);
return array();
}
}
}

View file

@ -0,0 +1,174 @@
<?php
# low level test of PHP functions & methods w/o WP integration
class LowLevelTests extends PHPUnit_Framework_TestCase {
function setUp() {
$this->FFM = new FF_Meta(new MockDataService());
$this->FFM->reinit_external_data_service(new MockDataService());
}
/* some very basic things */
function test_basic_json_parsing() {
$json = file_get_contents(__DIR__.'/example_ffhh.json');
$data = json_decode($json, $assoc = true);
$this->assertArrayHasKey('name', $data);
$this->assertArrayHasKey('state', $data);
$this->assertArrayHasKey('location', $data);
$this->assertArrayHasKey('services', $data);
}
function test_externaldata_mock() {
$ed = new MockDataService();
$url_dir = 'https://raw.githubusercontent.com/freifunk/directory.api.freifunk.net/master/directory.json';
$url_ff = 'http://meta.hamburg.freifunk.net/ffhh.json';
$url_inv = 'http://meta.hamburg.freifunk.net/invalid.txt';
// verify that $ed->get does not read the URLs above, but local example_*.json files
$data_ff = $ed->get($url_ff);
$this->assertArrayHasKey('name', $data_ff);
$this->assertArrayHasKey('state', $data_ff);
$this->assertArrayHasKey('location', $data_ff);
$this->assertArrayHasKey('services', $data_ff);
$data_dir = $ed->get($url_dir);
$this->assertArrayHasKey('hamburg', $data_dir);
$this->assertEquals(2, count($data_dir));
$data_inv = $ed->get($url_inv);
$this->assertEquals(0, count($data_inv));
}
/* the aux. classes */
function test_ff_directory() {
$dir = new FF_Directory(new MockDataService());
$valid = $dir->get_url_by_city('hamburg');
$invalid = $dir->get_url_by_city('jena');
$this->assertTrue(!!$valid);
$this->assertTrue(!$invalid);
}
/**
* @expectedException PHPUnit_Framework_Error
*/
function test_ff_community_invalid() {
$data = array();
$comm = new FF_Community($data);
}
function test_ff_community_empty() {
$data = array('location' => array());
$comm = new FF_Community($data);
$this->assertEmpty($comm->street);
$this->assertEmpty($comm->name);
$string = $comm->format_address();
$this->assertEquals('', $string);
}
function test_ff_community_filled() {
$data = array('location' => array(
'address' => array(
'Name' => 'some_name',
'Street' => 'some_street',
'Zipcode' => 'some_zip'
),
'city' => 'some_city',
'lon' => 'some_lon',
'lat' => 'some_lat',
));
$comm = new FF_Community($data);
$this->assertEquals('some_name', $comm->name);
$this->assertEquals('some_street', $comm->street);
$this->assertEquals('some_zip', $comm->zip);
$this->assertEquals('some_city', $comm->city);
$this->assertEquals('some_lon', $comm->lon);
$this->assertEquals('some_lat', $comm->lat);
$string = $comm->format_address();
$this->assertEquals('<p>some_name<br/>some_street<br/>some_zip some_city</p>', $string);
}
function test_ff_community_make_from_city() {
$comm = FF_Community::make_from_city('hamburg', new MockDataService());
$this->assertEquals('Chaos Computer Club Hansestadt Hamburg', $comm->name);
$this->assertEquals('Humboldtstr. 53', $comm->street);
$this->assertEquals('22083', $comm->zip);
$this->assertEquals('Hamburg', $comm->city);
$this->assertEquals(10.024418, $comm->lon);
$this->assertEquals(53.574267, $comm->lat);
}
/* the output methods */
function test_output_ff_state_null() {
$data = array("state" => array("nodes" => null));
$ret = $this->FFM->output_ff_state($data);
$this->assertEmpty($ret);
}
function test_output_ff_state() {
$data = array("state" => array("nodes" => 429));
$ret = $this->FFM->output_ff_state($data);
$this->assertRegExp('/429/', $ret);
}
function test_output_ff_services_null() {
$data = array();
$ret = $this->FFM->output_ff_services($data);
$this->assertEmpty($ret);
$this->assertEquals('', $ret);
}
function test_output_ff_services() {
$data = array(
'services' => array(array(
'serviceName' => 'jabber',
'serviceDescription' => 'chat',
'internalUri' => 'xmpp://jabber.local',
)));
$ret = $this->FFM->output_ff_services($data);
$this->assertEquals('<ul><li>jabber (chat): <a href="xmpp://jabber.local">xmpp://jabber.local</a></li></ul>', $ret);
}
function test_output_ff_contact_null() {
$data = array();
$ret = $this->FFM->output_ff_contact($data);
$this->assertEquals('', $ret);
}
function test_output_ff_contact_filled() {
$data = array('contact' => array(
'email' => 'mail@example.com',
'jabber' => 'example@freifunk.net'
));
$ret = $this->FFM->output_ff_contact($data);
$this->assertRegExp('/E-Mail/', $ret);
$this->assertRegExp('/mailto:mail@example\.com/', $ret);
$this->assertRegExp('/XMPP/', $ret);
$this->assertRegExp('/xmpp:example/', $ret);
$data = array('contact' => array(
'twitter' => 'http://twitter.com/freifunk'
));
$ret = $this->FFM->output_ff_contact($data);
$this->assertRegExp('/twitter\.com\/freifunk/', $ret);
$data = array('contact' => array(
'twitter' => '@freifunk'
));
$ret = $this->FFM->output_ff_contact($data);
$this->assertRegExp('/Twitter/', $ret);
$this->assertRegExp('/twitter\.com\/freifunk/', $ret);
$data = array('contact' => array(
'ml' => 'mail@example.com',
'irc' => 'irc://irc.hackint.net/example',
'facebook' => 'freifunk',
));
$ret = $this->FFM->output_ff_contact($data);
$this->assertRegExp('/mailto:mail@example\.com/', $ret);
$this->assertRegExp('/irc\.hackint\.net\/example/', $ret);
$this->assertRegExp('/Facebook:/', $ret);
}
}

View file

@ -0,0 +1,70 @@
<?php
# tests with WP integration, using the loaded plugin
class WpIntegrationTests extends WP_UnitTestCase {
function setUp() {
parent::setUp();
// access to plugin instance
$this->plugin = $GLOBALS['wp-plugin-ffmeta'];
$this->plugin->reinit_external_data_service(new MockDataService());
}
function test_post_ff_state() {
$post_content = '[ff_state]';
$post_attribs = array( 'post_title' => 'Test', 'post_content' => $post_content );
$post = $this->factory->post->create_and_get( $post_attribs );
// w/o filter:
$this->assertEquals($post_content, $post->post_content);
// with filter:
$output = apply_filters( 'the_content', $post->post_content );
$this->assertEquals("<div class=\"ff ff_state\">429</div>\n", $output);
}
function test_post_ff_state_othercity() {
$post_content = '[ff_state ffm]';
$post_attribs = array( 'post_title' => 'Test', 'post_content' => $post_content );
$post = $this->factory->post->create_and_get( $post_attribs );
$output = apply_filters( 'the_content', $post->post_content );
$this->assertEquals("<div class=\"ff ff_state\"></div>\n", $output);
}
function test_post_ff_state_inv_city() {
$post_content = '[ff_state jena]';
$post_attribs = array( 'post_title' => 'Test', 'post_content' => $post_content );
$post = $this->factory->post->create_and_get( $post_attribs );
$output = apply_filters( 'the_content', $post->post_content );
$this->assertRegExp('/<!-- FF Meta Error:/', $output);
}
function test_post_ff_services() {
$post_content = '[ff_services]';
$post_attribs = array( 'post_title' => 'Test', 'post_content' => $post_content );
$post = $this->factory->post->create_and_get( $post_attribs );
// w/o filter:
$this->assertEquals($post_content, $post->post_content);
// with filter:
$output = apply_filters( 'the_content', $post->post_content );
$this->assertRegExp('/radio\.ffhh/', $output);
}
function test_post_ff_list() {
$post_content = '[ff_list]';
$post_attribs = array( 'post_title' => 'Test', 'post_content' => $post_content );
$post = $this->factory->post->create_and_get( $post_attribs );
// w/o filter:
$this->assertEquals($post_content, $post->post_content);
// with filter:
$output = apply_filters( 'the_content', $post->post_content );
$this->assertRegExp('/Hamburg/', $output);
$this->assertRegExp('/Frankfurt/', $output);
}
}

View file

@ -1,43 +0,0 @@
<?php
# low level test of PHP functions & methods w/o WP integration
class LowLevelTest extends PHPUnit_Framework_TestCase {
function setUp() {
$this->FFM = new FF_Meta();
}
function test_output_ff_state() {
$ret = $this->FFM->output_ff_state(array("state" => array("nodes" => 429)));
$this->assertRegExp('/429/', $ret);
}
function test_basic_json_parsing() {
$json = file_get_contents(__DIR__.'/example_ffhh.json');
$data = json_decode($json, $assoc = true);
$this->assertArrayHasKey('name', $data);
$this->assertArrayHasKey('state', $data);
$this->assertArrayHasKey('location', $data);
$this->assertArrayHasKey('services', $data);
}
function test_externaldata() {
$json = file_get_contents(__DIR__.'/example_ffhh.json');
$stubdata = json_decode($json, $assoc = true);
$stub = $this->getMockBuilder('ff_meta_externaldata')
->disableOriginalConstructor()
->getMock();
$stub->expects($this->any())
->method('get')
->will($this->returnValue($stubdata));
$data = $stub->get('http://meta.hamburg.freifunk.net/ffhh.json');
$this->assertArrayHasKey('name', $data);
$this->assertArrayHasKey('state', $data);
$this->assertArrayHasKey('location', $data);
$this->assertArrayHasKey('services', $data);
}
}

View file

@ -1,8 +0,0 @@
<?php
# all tests with WP integration
class SampleTest extends WP_UnitTestCase {
function test_sample() {
$this->assertTrue(true);
}
}