-
Notifications
You must be signed in to change notification settings - Fork 49
/
stack.php
327 lines (299 loc) · 9.5 KB
/
stack.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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
<?php
/**
* @package Habari
*
*/
/**
* Habari Stack Class
*
* This class allows Habari to accumulate a group of unique values that
* can be output using a specific formatting string.
* This is useful for collecting a set of unique javascript references to output
* and then insert them at a specific point on the page.
*
* <code>
* // Add jquery to the javascript stack:
* Stack::add( 'template_header_javascript', Site::get_url('scripts') . '/jquery.js', 'jquery' );
*
* // Add stylesheet to theme_stylesheet stack with media type
* Stack::add( 'template_stylesheet', array( Site::get_url('theme') . '/style.css', 'screen' ), 'style' );
*
* // Output the javascript stack:
* Stack::out( 'template_header_javascript', '<script src="%s" type="text/javascript"></script>' );
*
* // Output the theme_stylesheet stack:
* Stack::out( 'template_stylesheet', '<link rel="stylesheet" type="text/css" href="%s" media="%s">' );
* </code>
*
*/
class Stack
{
private static $stacks = array();
private static $stack_sort = array();
private static $sorting;
/**
* Private constructor for Stack.
* Stack objects should only be created using the static
* method Static::create_stack(), or one of the methods that
* adds a value directly to a stack. This prevents multiple Stack
* objects from being created with the same name.
*
* @param mixed $input An array or ArrayObject to create the stack from.
* @return array The created stack
*/
private function __construct( $input )
{
parent::__construct( $input );
}
/**
* Retreive a named stack instance
* @param string $stack_name The name of the stack to return
* @return Stack The requested stack
**/
public static function get_named_stack( $stack_name )
{
if ( isset( self::$stacks[$stack_name] ) ) {
return self::$stacks[$stack_name];
}
else {
return self::create_stack( $stack_name );
}
}
/**
* Check for the existence of a given stack item.
*
* @param string $stack_name The name of the stack in which to check.
* @param string $value The value to check for.
* @return boolean true if the item exists, false otherwise.
*/
public static function has ( $stack_name, $value_name )
{
// get the stack
$stack = self::get_named_stack( $stack_name );
if ( isset( $stack[ $value_name ] ) ) {
return true;
}
else {
return false;
}
}
/**
* Get a single item from a given stack.
*
* @param string $stack_name The name of the stack to fetch an item from.
* @param string $value The item to fetch.
* @param mixed $default_value The default value to return if the item does not exist in the stack.
* @return mixed The item, or $default_value if it does not exist.
*/
public static function get_item ( $stack_name, $value_name, $default_value = null )
{
// get the stack
$stack = self::get_named_stack( $stack_name );
if ( isset( $stack[ $value_name ] ) ) {
return $stack[ $value_name ];
}
else {
return $default_value;
}
}
/**
* Creates and retreives a named stack instance
* @param string $stack_name The name of the stack to create and return
* @return array The created stack
**/
public static function create_stack( $stack_name )
{
if ( empty( self::$stacks[$stack_name] ) ) {
$stack = array();
self::$stacks[$stack_name] = $stack;
self::$stack_sort[$stack_name] = array();
}
return self::$stacks[$stack_name];
}
/**
* Add a value to a stack
* @param string $stack_name The name of the stack
* @param mixed $value The value to add
* @param string $value_name The name of the value to add
* @param string $after The name of the stack element to insert this new element after
* @return array The stack that was added to
**/
public static function add( $stack_name, $value, $value_name = null, $after = null )
{
$stack = self::get_named_stack( $stack_name );
$value_name = $value_name ? $value_name : md5( serialize( $value ) );
if ( !is_null( $after ) ) {
if ( !is_array( $after ) ) {
$after = array( $after );
}
foreach ( $after as $a ) {
self::depend($stack_name, $value_name, $a);
}
}
$stack[$value_name] = $value;
self::$stacks[$stack_name] = $stack;
return $stack;
}
/**
* Add a named stack item to the list of things it depends on
* @static
* @param string $stack_name The name of the stack
* @param string $value_name The item name in the stack
* @param string $value_name_on The item name on which this item depends
*/
public static function depend( $stack_name, $value_name, $value_name_on )
{
if ( !isset( self::$stack_sort[$stack_name] ) ) {
self::$stack_sort[$stack_name] = array();
}
if ( !isset( self::$stack_sort[$stack_name][$value_name] ) ) {
self::$stack_sort[$stack_name][$value_name] = array();
}
self::$stack_sort[$stack_name][$value_name][$value_name_on] = $value_name_on;
}
/**
* Remove a value to a stack
* @param string $stack_name The name of the stack
* @param string $value_name The name of the value to remove
* @return array The rest of the stack, post-remove
**/
public static function remove( $stack_name, $value_name = null )
{
if ( $value_name == null ) {
unset( self::$stacks[ $stack_name ] );
return array();
}
$stack = self::get_named_stack( $stack_name );
if ( isset( $stack[$value_name] ) ) {
unset( $stack[$value_name] );
}
self::$stacks[$stack_name] = $stack;
return $stack;
}
public static function get_sorted_stack( $stack_name )
{
self::$sorting = $stack_name;
$stack = self::get_named_stack( $stack_name );
$sorted = array();
$depdata = self::$stack_sort[ $stack_name ];
$lastcount = 0;
$failed = false;
while(count($sorted) < count($stack)) {
foreach($stack as $itemkey => $value) {
if(isset($sorted[$itemkey])) {
continue;
}
if(!$failed && isset($depdata[$itemkey])) {
$requires = $depdata[$itemkey];
$requires = array_combine($depdata[$itemkey], $depdata[$itemkey]);
if(count(array_intersect_key($requires, $sorted)) == count($requires)) {
$sorted[$itemkey] = $value;
}
}
else {
$sorted[$itemkey] = $value;
}
}
if($lastcount == count($sorted)) {
$failed = true;
}
$lastcount = count($sorted);
}
return $sorted;
}
/**
* Returns all of the values of the stack
* @param string $stack_name The name of the stack to output
* @param mixed $format A printf-style formatting string or callback used to output each stack element
**/
public static function get( $stack_name, $format = null )
{
$out = '';
$stack = self::get_sorted_stack( $stack_name );
$stack = Plugins::filter( 'stack_out', $stack, $stack_name, $format );
foreach ( $stack as $element ) {
if ( is_callable( $format ) ) {
$out.= call_user_func_array( $format, (array) $element );
}
elseif ( is_string( $format ) ) {
$out .= vsprintf( $format, (array) $element );
}
else {
$out.= $element;
}
}
return $out;
}
/**
* Outputs all of the values of the stack
* @param string $stack_name The name of the stack to output
* @param mixed $format A printf-style formatting string or callback used to output each stack element
**/
public static function out( $stack_name, $format = null )
{
echo self::get( $stack_name, $format );
}
/**
* A callback for Stack::get() that outputs scripts as reference or inline depending on their content
*
* @param string $element The script element in the stack
* @param mixed $attrib Additional attributes, like 'defer' or 'async' allowed for <script src=...> tags
* @param string $wrapper An sprintf formatting string in which to output the script tag, for IE conditional comments
* @return string The resulting script tag
*/
public static function scripts( $element, $attrib = null, $wrapper = '%s' )
{
if(is_array($attrib)) {
$attrib = $attrib + array('type' => 'text/javascript');
$attrib = Utils::html_attr($attrib);
}
else {
$attrib .= ' type="text/javascript"';
}
if ( self::is_url( $element ) ) {
$output = sprintf( "<script %s src=\"%s\"></script>\r\n", $attrib, $element );
}
else {
$output = sprintf( "<script %s>%s</script>\r\n", $attrib, $element );
}
$output = sprintf($wrapper, $output);
return $output;
}
/**
* A callback for Stack::get() that outputs styles as link or inline style tags depending on their content
*
* @param string $element The style element in the stack
* @param string $typename The media disposition of the content
* @param string $props Additional properties of the style tag output
* @return string The resulting style or link tag
*/
public static function styles( $element, $typename = null, $props = array() )
{
$props = $props + array('type' => 'text/css');
if ( !empty( $typename ) ) {
$props['media'] = $typename;
}
if ( self::is_url( $element ) ) {
$props = $props + array('rel' => 'stylesheet', 'href' => $element);
$output = sprintf( "<link %s>\r\n", Utils::html_attr($props) );
}
else {
$output = sprintf( "<style %2\$s>%1\$s</style>\r\n", $element, Utils::html_attr($props) );
}
return $output;
}
/**
* Check if the passed string looks like a URL or an absolute path to a file.
*
* @todo There's a good chance this can be done in a better or more generic
* way.
*
* @param string $url The string to check.
* @return boolean TRUE if the passed string looks like a URL.
*/
private static function is_url( $url )
{
return ( ( strpos( $url, 'http://' ) === 0 || strpos( $url, 'https://' ) === 0 || strpos( $url, '//' ) === 0 || strpos( $url, '/' ) === 0 ) && strpos( $url, "\n" ) === false );
}
}
?>