Issues (2756)

admin/index.php (7 issues)

1
<?php
2
define( 'YOURLS_ADMIN', true );
3
require_once( dirname( __DIR__ ).'/includes/load-yourls.php' );
4
yourls_maybe_require_auth();
5
6
// Variables
7
$table_url = YOURLS_DB_TABLE_URL;
8
$search_sentence = $search_text = $url = $keyword = '';
9
/* $where will collect additional SQL arguments:
0 ignored issues
show
Unused Code Comprehensibility introduced by
41% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
10
 * $where['sql'] will concatenate SQL clauses: $where['sql'] .= ' AND something = :value ';
11
 * $where['binds'] will hold the (name => value) placeholder pairs: $where['binds']['value'] = $value;
12
 */
13
$where = array('sql' => '', 'binds' => array());
14
$date_filter = $date_first  = $date_second = '';
15
$base_page   = yourls_admin_url( 'index.php' );
16
17
// Default SQL behavior
18
$search_in_text  = yourls__( 'URL' );
19
$search_in       = 'all';
20
$sort_by_text    = yourls__( 'Short URL' );
21
$sort_by         = 'timestamp';
22
$sort_order      = 'desc';
23
$page            = ( isset( $_GET['page'] ) ? intval($_GET['page']) : 1 );
24
$search          = yourls_get_search_text();
25
$perpage         = ( isset( $_GET['perpage'] ) && intval( $_GET['perpage'] ) ? intval($_GET['perpage']) : yourls_apply_filter( 'admin_view_per_page', 15 ) );
26
$click_limit     = ( isset( $_GET['click_limit'] ) && $_GET['click_limit'] !== '' ) ? intval( $_GET['click_limit'] ) : '' ;
27
if ( $click_limit !== '' ) {
28
	$click_filter   = ( isset( $_GET['click_filter'] ) && $_GET['click_filter'] == 'more' ? 'more' : 'less' ) ;
29
	$click_moreless = ( $click_filter == 'more' ? '>' : '<' );
30
	$where['sql']   = " AND clicks $click_moreless :click_limit";
31
    $where['binds']['click_limit'] = $click_limit;
32
} else {
33
	$click_filter   = '';
34
}
35
36
// Searching
37
if( !empty( $search ) && !empty( $_GET['search_in'] ) ) {
38
	switch( $_GET['search_in'] ) {
39
		case 'all':
40
			$search_in_text = yourls__( 'All fields' );
41
			$search_in      = 'all';
42
			break;
43
		case 'keyword':
44
			$search_in_text = yourls__( 'Short URL' );
45
			$search_in      = 'keyword';
46
			break;
47
		case 'url':
48
			$search_in_text = yourls__( 'URL' );
49
			$search_in      = 'url';
50
			break;
51
		case 'title':
52
			$search_in_text = yourls__( 'Title' );
53
			$search_in      = 'title';
54
			break;
55
		case 'ip':
56
			$search_in_text = yourls__( 'IP Address' );
57
			$search_in      = 'ip';
58
			break;
59
	}
60
	$search_sentence = yourls_s( 'Searching for <strong>%1$s</strong> in <strong>%2$s</strong>.', yourls_esc_html( $search ), yourls_esc_html( $search_in_text ) );
61
	$search_text     = $search;
62
	$search          = str_replace( '*', '%', '*' . $search . '*' );
63
    if( $search_in == 'all' ) {
64
        $where['sql'] .= ' AND CONCAT_WS("",`keyword`,`url`,`title`,`ip`) LIKE (:search)';
65
        $where['binds']['search'] = $search;
66
        // Search across all fields. The resulting SQL will be something like:
67
        // SELECT * FROM `yourls_url` WHERE CONCAT_WS('',`keyword`,`url`,`title`,`ip`) LIKE ("%ozh%")
68
        // CONCAT_WS because CONCAT('foo', 'bar', NULL) = NULL. NULL wins. Not sure if values can be NULL now or in the future, so better safe.
69
        // TODO: pay attention to this bit when the DB schema changes
70
    } else {
71
        $where['sql'] .= " AND `$search_in` LIKE (:search)";
72
        $where['binds']['search'] = $search;
73
    }
74
}
75
76
// Time span
77
if( !empty( $_GET['date_filter'] ) ) {
78
	switch( $_GET['date_filter'] ) {
79
		case 'before':
80
			$date_filter = 'before';
81
			if( isset( $_GET['date_first'] ) && yourls_sanitize_date( $_GET['date_first'] ) ) {
82
				$date_first     = yourls_sanitize_date( $_GET['date_first'] );
83
				$date_first_sql = yourls_sanitize_date_for_sql( $_GET['date_first'] );
84
				$where['sql'] .= ' AND `timestamp` < :date_first_sql';
85
                $where['binds']['date_first_sql'] = $date_first_sql;
86
			}
87
			break;
88
		case 'after':
89
			$date_filter = 'after';
90
			if( isset( $_GET['date_first'] ) && yourls_sanitize_date( $_GET['date_first'] ) ) {
91
				$date_first_sql = yourls_sanitize_date_for_sql( $_GET['date_first'] );
92
				$date_first     = yourls_sanitize_date( $_GET['date_first'] );
93
				$where['sql'] .= ' AND `timestamp` > :date_first_sql';
94
                $where['binds']['date_first_sql'] = $date_first_sql;
95
			}
96
			break;
97
		case 'between':
98
			$date_filter = 'between';
99
			if( isset( $_GET['date_first'] ) && isset( $_GET['date_second'] ) && yourls_sanitize_date( $_GET['date_first'] ) && yourls_sanitize_date( $_GET['date_second'] ) ) {
100
				$date_first_sql  = yourls_sanitize_date_for_sql( $_GET['date_first'] );
101
				$date_second_sql = yourls_sanitize_date_for_sql( $_GET['date_second'] );
102
				$date_first      = yourls_sanitize_date( $_GET['date_first'] );
103
				$date_second     = yourls_sanitize_date( $_GET['date_second'] );
104
				$where['sql'] .= ' AND `timestamp` BETWEEN :date_first_sql AND :date_second_sql';
105
                $where['binds']['date_first_sql']  = $date_first_sql;
106
                $where['binds']['date_second_sql'] = $date_second_sql;
107
			}
108
			break;
109
	}
110
}
111
112
// Sorting
113
if( !empty( $_GET['sort_by'] ) || !empty( $_GET['sort_order'] ) ) {
114
	switch( $_GET['sort_by'] ) {
115
		case 'keyword':
116
			$sort_by_text = yourls__( 'Short URL' );
117
			$sort_by      = 'keyword';
118
			break;
119
		case 'url':
120
			$sort_by_text = yourls__( 'URL' );
121
			$sort_by      = 'url';
122
			break;
123
		case 'title':
124
			$sort_by_text = yourls__( 'Title' );
125
			$sort_by      = 'title';
126
			break;
127
		case 'timestamp':
128
			$sort_by_text = yourls__( 'Date' );
129
			$sort_by      = 'timestamp';
130
			break;
131
		case 'ip':
132
			$sort_by_text = yourls__( 'IP Address' );
133
			$sort_by      = 'ip';
134
			break;
135
		case 'clicks':
136
			$sort_by_text = yourls__( 'Clicks' );
137
			$sort_by      = 'clicks';
138
			break;
139
	}
140
	switch( $_GET['sort_order'] ) {
141
		case 'asc':
142
			$sort_order      = 'asc';
143
			break;
144
		case 'desc':
145
			$sort_order      = 'desc';
146
			break;
147
	}
148
}
149
150
// Get URLs Count for current filter, total links in DB & total clicks
151
list( $total_urls, $total_clicks ) = array_values( yourls_get_db_stats() );
152
if ( !empty($where['sql']) ) {
153
	list( $total_items, $total_items_clicks ) = array_values( yourls_get_db_stats( $where ) );
154
} else {
155
	$total_items        = $total_urls;
156
	$total_items_clicks = false;
157
}
158
159
// This is a bookmarklet
160
if ( isset( $_GET['u'] ) or isset( $_GET['up'] ) ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
161
	$is_bookmark = true;
162
	yourls_do_action( 'bookmarklet' );
163
164
	// No sanitization needed here: everything happens in yourls_add_new_link()
165
	if( isset( $_GET['u'] ) ) {
166
		// Old school bookmarklet: ?u=<url>
167
		$url = urldecode( $_GET['u'] );
168
	} else {
169
		// New style bookmarklet: ?up=<url protocol>&us=<url slashes>&ur=<url rest>
170
		$url = urldecode( $_GET['up'] . $_GET['us'] . $_GET['ur'] );
171
	}
172
	$keyword = ( isset( $_GET['k'] ) ? ( $_GET['k'] ) : '' );
173
	$title   = ( isset( $_GET['t'] ) ? ( $_GET['t'] ) : '' );
174
	$return  = yourls_add_new_link( $url, $keyword, $title );
175
176
	// If fails because keyword already exist, retry with no keyword
177
	if ( isset( $return['status'] ) && $return['status'] == 'fail' && isset( $return['code'] ) && $return['code'] == 'error:keyword' ) {
178
		$msg = $return['message'];
179
		$return = yourls_add_new_link( $url, '' );
180
		$return['message'] .= ' ('.$msg.')';
181
	}
182
183
	// Stop here if bookmarklet with a JSON callback function
184
	if( isset( $_GET['jsonp'] ) && $_GET['jsonp'] == 'yourls' ) {
185
		$short   = $return['shorturl'] ? $return['shorturl'] : '';
186
		$message = $return['message'];
187
		yourls_content_type_header( 'application/javascript' );
188
		echo yourls_apply_filter( 'bookmarklet_jsonp', "yourls_callback({'short_url':'$short','message':'$message'});" );
189
190
		die();
191
	}
192
193
	// Now use the URL that has been sanitized and returned by yourls_add_new_link()
194
	$url = $return['url']['url'];
195
	$where['sql'] .= ' AND `url` LIKE :url ';
196
    $where['binds']['url'] = $url;
197
198
	$page   = $total_pages = $perpage = 1;
199
	$offset = 0;
200
201
	$text   = ( isset( $_GET['s'] ) ? stripslashes( $_GET['s'] ) : '' );
202
203
	// Sharing with social bookmarklets
204
	if( !empty($_GET['share']) ) {
205
		yourls_do_action( 'pre_share_redirect' );
206
		switch ( $_GET['share'] ) {
207
			case 'twitter':
208
				// share with Twitter
209
				$destination = sprintf( "https://twitter.com/intent/tweet?url=%s&text=%s", urlencode( $return['shorturl'] ), urlencode( $title ) );
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal https://twitter.com/intent/tweet?url=%s&text=%s does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
210
				yourls_redirect( $destination, 303 );
211
212
				// Deal with the case when redirection failed:
213
				$return['status']    = 'error';
214
				$return['errorCode'] = 400;
215
				$return['message']   = yourls_s( 'Short URL created, but could not redirect to %s !', 'Twitter' );
216
				break;
217
218
			case 'facebook':
219
				// share with Facebook
220
				$destination = sprintf( "https://www.facebook.com/sharer/sharer.php?u=%s&t=%s", urlencode( $return['shorturl'] ), urlencode( $title ) );
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal https://www.facebook.com...er/sharer.php?u=%s&t=%s does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
221
				yourls_redirect( $destination, 303 );
222
223
				// Deal with the case when redirection failed:
224
				$return['status']    = 'error';
225
				$return['errorCode'] = 400;
226
				$return['message']   = yourls_s( 'Short URL created, but could not redirect to %s !', 'Facebook' );
227
				break;
228
229
			case 'tumblr':
230
				// share with Tumblr
231
				$destination = sprintf( "https://www.tumblr.com/share?v=3&u=%s&t=%s&s=%s", urlencode( $return['shorturl'] ), urlencode( $title ), urlencode( $text ) );
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal https://www.tumblr.com/share?v=3&u=%s&t=%s&s=%s does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
232
				yourls_redirect( $destination, 303 );
233
234
				// Deal with the case when redirection failed:
235
				$return['status']    = 'error';
236
				$return['errorCode'] = 400;
237
				$return['message']   = yourls_s( 'Short URL created, but could not redirect to %s !', 'Tumblr' );
238
				break;
239
240
			default:
241
				// Is there a custom registered social bookmark?
242
				yourls_do_action( 'share_redirect_' . $_GET['share'], $return );
243
244
				// Still here? That was an unknown 'share' method, then.
245
				$return['status']    = 'error';
246
				$return['errorCode'] = 400;
247
				$return['message']   = yourls__( 'Unknown "Share" bookmarklet' );
248
				break;
249
		}
250
	}
251
252
// This is not a bookmarklet
253
} else {
254
	$is_bookmark = false;
255
256
	// Checking $page, $offset, $perpage
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
257
	if( empty($page) || $page == 0 ) {
258
		$page = 1;
259
	}
260
	if( empty($offset) ) {
261
		$offset = 0;
262
	}
263
	if( empty($perpage) || $perpage == 0) {
264
		$perpage = 50;
265
	}
266
267
	// Determine $offset
268
	$offset = ( $page-1 ) * $perpage;
269
270
	// Determine Max Number Of Items To Display On Page
271
	if( ( $offset + $perpage ) > $total_items ) {
272
		$max_on_page = $total_items;
273
	} else {
274
		$max_on_page = ( $offset + $perpage );
275
	}
276
277
	// Determine Number Of Items To Display On Page
278
	if ( ( $offset + 1 ) > $total_items ) {
279
		$display_on_page = $total_items;
280
	} else {
281
		$display_on_page = ( $offset + 1 );
282
	}
283
284
	// Determing Total Amount Of Pages
285
	$total_pages = ceil( $total_items / $perpage );
286
}
287
288
289
// Begin output of the page
290
$context = ( $is_bookmark ? 'bookmark' : 'index' );
291
yourls_html_head( $context );
292
yourls_html_logo();
293
yourls_html_menu() ;
294
295
yourls_do_action( 'admin_page_before_content' );
296
297
if ( !$is_bookmark ) { ?>
298
	<p><?php echo $search_sentence; ?></p>
299
	<p><?php
300
		printf( yourls__( 'Display <strong>%1$s</strong> to <strong class="increment">%2$s</strong> of <strong class="increment">%3$s</strong> URLs' ), $display_on_page, $max_on_page, $total_items );
301
		if( $total_items_clicks !== false )
302
			echo ", " . sprintf( yourls_n( 'counting <strong>1</strong> click', 'counting <strong>%s</strong> clicks', $total_items_clicks ), yourls_number_format_i18n( $total_items_clicks ) );
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal , does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
303
	?>.</p>
304
<?php } ?>
305
<p id="overall_tracking"><?php printf( yourls__( 'Overall, tracking <strong class="increment">%1$s</strong> links, <strong>%2$s</strong> clicks, and counting!' ), yourls_number_format_i18n( $total_urls ), yourls_number_format_i18n( $total_clicks ) ); ?></p>
306
<?php
307
308
yourls_do_action( 'admin_page_before_form' );
309
310
yourls_html_addnew();
311
312
// If bookmarklet, add message. Otherwise, hide hidden share box.
313
if ( !$is_bookmark ) {
314
	yourls_share_box( '', '', '', '', '', '', true );
315
} else {
316
	echo '<script type="text/javascript">$(document).ready(function(){
317
		feedback( "' . $return['message'] . '", "'. $return['status'] .'");
318
		init_clipboard();
319
	});</script>';
320
}
321
322
yourls_do_action( 'admin_page_before_table' );
323
324
yourls_table_head();
325
326
if ( !$is_bookmark ) {
327
	$params = array(
328
		'search'       => $search,
329
		'search_text'  => $search_text,
330
		'search_in'    => $search_in,
331
		'sort_by'      => $sort_by,
332
		'sort_order'   => $sort_order,
333
		'page'         => $page,
334
		'perpage'      => $perpage,
335
		'click_filter' => $click_filter,
336
		'click_limit'  => $click_limit,
337
		'total_pages'  => $total_pages,
338
		'date_filter'  => $date_filter,
339
		'date_first'   => $date_first,
340
		'date_second'  => $date_second,
341
	);
342
	yourls_html_tfooter( $params );
343
}
344
345
yourls_table_tbody_start();
346
347
// Main Query
348
$where = yourls_apply_filter( 'admin_list_where', $where );
349
$url_results = $ydb->fetchObjects( "SELECT * FROM `$table_url` WHERE 1=1 ${where['sql']} ORDER BY `$sort_by` $sort_order LIMIT $offset, $perpage;", $where['binds'] );
350
$found_rows = false;
351
if( $url_results ) {
352
	$found_rows = true;
353
	foreach( $url_results as $url_result ) {
354
		$keyword = yourls_sanitize_keyword($url_result->keyword);
355
		$timestamp = strtotime( $url_result->timestamp );
356
		$url = stripslashes( $url_result->url );
357
		$ip = $url_result->ip;
358
		$title = $url_result->title ? $url_result->title : '';
359
		$clicks = $url_result->clicks;
360
361
		echo yourls_table_add_row( $keyword, $url, $title, $ip, $clicks, $timestamp );
362
	}
363
}
364
365
$display = $found_rows ? 'display:none' : '';
366
echo '<tr id="nourl_found" style="'.$display.'"><td colspan="6">' . yourls__('No URL') . '</td></tr>';
367
368
yourls_table_tbody_end();
369
370
yourls_table_end();
371
372
yourls_do_action( 'admin_page_after_table' );
373
374
if ( $is_bookmark )
375
	yourls_share_box( $url, $return['shorturl'], $title, $text );
376
?>
377
378
<?php yourls_html_footer( ); ?>
379