Completed
Push — master ( 2a3d9e...0c2493 )
by Andrey
01:58
created

Toolbar_Theme_Switcher::get_theme_field()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
1
<?php
2
3
/**
4
 * Main plugin class.
5
 */
6
class Toolbar_Theme_Switcher {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
7
8
	/** @var WP_Theme $theme */
9
	public static $theme = false;
10
11
	/**
12
	 * Hooks that need to be set up early.
13
	 */
14
	public static function on_load() {
15
16
		add_action( 'setup_theme', array( __CLASS__, 'setup_theme' ) );
17
		add_action( 'init', array( __CLASS__, 'init' ) );
18
	}
19
20
	/**
21
	 * Loads cookie and sets up theme filters.
22
	 */
23
	public static function setup_theme() {
0 ignored issues
show
Coding Style introduced by
setup_theme uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
24
25
		global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
26
27
		if ( ( is_admin() && 'themes.php' == $pagenow ) || ! self::can_switch_themes() ) {
28
			return;
29
		}
30
31
		if ( isset( $_GET['tts_reset'] ) ) {
32
			setcookie( self::get_cookie_name(), '', 1 );
33
			nocache_headers();
34
			wp_safe_redirect( home_url() );
35
			die;
1 ignored issue
show
Coding Style Compatibility introduced by
The method setup_theme() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
36
		}
37
38
		self::load_cookie();
39
40
		if ( empty( self::$theme ) ) {
41
			return;
42
		}
43
44
		add_filter( 'pre_option_template', array( self::$theme, 'get_template' ) );
45
		add_filter( 'pre_option_stylesheet', array( self::$theme, 'get_stylesheet' ) );
46
		add_filter( 'pre_option_stylesheet_root', array( self::$theme, 'get_theme_root' ) );
47
		$parent = self::$theme->parent();
48
		add_filter( 'pre_option_template_root', array( empty( $parent ) ? self::$theme : $parent, 'get_theme_root' ) );
49
		add_filter( 'pre_option_current_theme', '__return_false' );
50
	}
51
52
	/**
53
	 * If allowed to switch theme.
54
	 *
55
	 * @return boolean
56
	 */
57
	public static function can_switch_themes() {
58
59
		$capability = apply_filters( 'tts_capability', 'switch_themes' );
60
61
		return apply_filters( 'tts_can_switch_themes', current_user_can( $capability ) );
62
	}
63
64
	/**
65
	 * Sets if cookie is defined to non-default theme.
66
	 */
67
	public static function load_cookie() {
0 ignored issues
show
Coding Style introduced by
load_cookie uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
68
69
		$cookie_name = self::get_cookie_name();
70
71
		if ( empty( $_COOKIE[ $cookie_name ] ) ) {
72
			return;
73
		}
74
75
		$theme = wp_get_theme( $_COOKIE[ $cookie_name ] );
76
77
		if (
78
			$theme->exists()
79
			&& $theme->get( 'Name' ) != get_option( 'current_theme' )
80
			&& $theme->is_allowed()
81
		) {
82
			self::$theme = $theme;
83
		}
84
	}
85
86
	/**
87
	 * Returns cookie name, based on home URL so it differs for sites in multisite.
88
	 *
89
	 * @return string
90
	 */
91
	public static function get_cookie_name() {
92
93
		static $hash;
94
95
		if ( empty( $hash ) ) {
96
			$hash = 'wordpress_tts_theme_' . md5( home_url( '', 'http' ) );
97
		}
98
99
		return $hash;
100
	}
101
102
	/**
103
	 * Retrieves allowed themes.
104
	 *
105
	 * @return WP_Theme[]
106
	 */
107
	public static function get_allowed_themes() {
108
109
		static $themes;
110
111
		if ( isset( $themes ) ) {
112
			return $themes;
113
		}
114
115
		$wp_themes = wp_get_themes( array( 'allowed' => true ) );
116
117
		/** @var WP_Theme $theme */
118
		foreach ( $wp_themes as $theme ) {
119
120
			// Make keys names (rather than slugs) for backwards compat.
121
			$themes[ $theme->get( 'Name' ) ] = $theme;
122
		}
123
124
		$themes = apply_filters( 'tts_allowed_themes', $themes );
125
126
		return $themes;
127
	}
128
129
	/**
130
	 * Sets up hooks that doesn't need to happen early.
131
	 */
132
	public static function init() {
133
134
		if ( self::can_switch_themes() ) {
135
			add_action( 'admin_bar_menu', array( __CLASS__, 'admin_bar_menu' ), 90 );
136
			add_action( 'wp_ajax_tts_set_theme', array( __CLASS__, 'set_theme' ) );
137
		}
138
139
		load_plugin_textdomain( 'toolbar-theme-switcher', false, dirname( plugin_basename( __FILE__ ) ) . '/lang' );
140
	}
141
142
	/**
143
	 * Creates menu in toolbar.
144
	 *
145
	 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance.
146
	 */
147
	public static function admin_bar_menu( $wp_admin_bar ) {
148
		$themes  = self::get_allowed_themes();
149
		$current = empty( self::$theme ) ? wp_get_theme() : self::$theme;
150
		unset( $themes[ $current->get( 'Name' ) ] );
151
		uksort( $themes, array( __CLASS__, 'sort_core_themes' ) );
152
153
		$title = apply_filters( 'tts_root_title', sprintf( __( 'Theme: %s', 'toolbar-theme-switcher' ), $current->display( 'Name' ) ) );
154
155
		$wp_admin_bar->add_menu( array(
156
			'id'    => 'toolbar_theme_switcher',
157
			'title' => $title,
158
			'href'  => admin_url( 'themes.php' ),
159
		) );
160
161
		$ajax_url = admin_url( 'admin-ajax.php' );
162
163
		foreach ( $themes as $theme ) {
164
165
			$href = add_query_arg( array(
166
				'action' => 'tts_set_theme',
167
				'theme'  => urlencode( $theme->get_stylesheet() ),
168
			), $ajax_url );
169
170
			$wp_admin_bar->add_menu( array(
171
				'id'     => $theme['Stylesheet'],
172
				'title'  => $theme->display( 'Name' ),
173
				'href'   => $href,
174
				'parent' => 'toolbar_theme_switcher',
175
			) );
176
		}
177
	}
178
179
	/**
180
	 * Callback to sort theme array with core themes in numerical order by year.
181
	 *
182
	 * @param string $theme_a First theme name.
183
	 * @param string $theme_b Second theme name.
184
	 *
185
	 * @return int
186
	 */
187
	public static function sort_core_themes( $theme_a, $theme_b ) {
188
189
		static $twenties = array(
190
			'Twenty Ten',
191
			'Twenty Eleven',
192
			'Twenty Twelve',
193
			'Twenty Thirteen',
194
			'Twenty Fourteen',
195
			'Twenty Fifteen',
196
			'Twenty Sixteen',
197
			'Twenty Seventeen',
198
			'Twenty Eighteen',
199
			'Twenty Nineteen',
200
			'Twenty Twenty',
201
		);
202
203
		if ( 0 === strpos( $theme_a, 'Twenty' ) && 0 === strpos( $theme_b, 'Twenty' ) ) {
204
205
			$index_a = array_search( $theme_a, $twenties, true );
206
			$index_b = array_search( $theme_b, $twenties, true );
207
208
			if ( false !== $index_a || false !== $index_b ) {
209
				return ( $index_a < $index_b ) ? - 1 : 1;
210
			}
211
		}
212
213
		return strcasecmp( $theme_a, $theme_b );
214
	}
215
216
	/**
217
	 * Saves selected theme in cookie if valid.
218
	 */
219
	public static function set_theme() {
0 ignored issues
show
Coding Style introduced by
set_theme uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
220
221
		$stylesheet = $_REQUEST['theme'];
222
		$theme      = wp_get_theme( $stylesheet );
223
224
		if ( $theme->exists() && $theme->is_allowed() ) {
225
			setcookie( self::get_cookie_name(), $theme->get_stylesheet(), strtotime( '+1 year' ), COOKIEPATH );
226
		}
227
228
		wp_safe_redirect( wp_get_referer() );
229
		die;
0 ignored issues
show
Coding Style Compatibility introduced by
The method set_theme() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
230
	}
231
232
	// <editor-fold desc="Deprecated">
233
	/**
234
	 * If theme is in list of allowed to be switched to.
235
	 *
236
	 * @deprecated :2.0
237
	 *
238
	 * @param WP_Theme $theme
239
	 *
240
	 * @return bool
241
	 */
242
	public static function is_allowed( $theme ) {
243
244
		return array_key_exists( $theme->get( 'Name' ), self::get_allowed_themes() );
245
	}
246
247
	/**
248
	 * Template slug filter.
249
	 *
250
	 * @param string $template
251
	 *
252
	 * @deprecated :2.0
253
	 *
254
	 * @return string
255
	 */
256
	public static function template( $template ) {
257
258
		return self::get_theme_field( 'Template', $template );
0 ignored issues
show
Deprecated Code introduced by
The method Toolbar_Theme_Switcher::get_theme_field() has been deprecated with message: :2.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
259
	}
260
261
	/**
262
	 * Stylesheet slug filter.
263
	 *
264
	 * @param string $stylesheet
265
	 *
266
	 * @deprecated :2.0
267
	 *
268
	 * @return string
269
	 */
270
	public static function stylesheet( $stylesheet ) {
271
272
		return self::get_theme_field( 'Stylesheet', $stylesheet );
0 ignored issues
show
Deprecated Code introduced by
The method Toolbar_Theme_Switcher::get_theme_field() has been deprecated with message: :2.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
273
	}
274
275
	/**
276
	 * Returns field from theme data if cookie is set to valid theme.
277
	 *
278
	 * @param string $field_name
279
	 * @param mixed  $default
280
	 *
281
	 * @deprecated :2.0
282
	 *
283
	 * @return mixed
284
	 */
285
	public static function get_theme_field( $field_name, $default = false ) {
286
287
		if ( ! empty( self::$theme ) ) {
288
			return self::$theme->get( $field_name );
289
		}
290
291
		return $default;
292
	}
293
	// </editor-fold>
294
}
295