Merge lp:~vasilev/psiphon/sprint3-579678 into lp:psiphon
- sprint3-579678
- Merge into trunk
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~vasilev/psiphon/sprint3-579678 | ||||
Merge into: | lp:psiphon | ||||
Diff against target: |
338 lines (+215/-16) 4 files modified
trunk/www/config.php (+1/-0) trunk/www/includes/lang.php (+65/-0) trunk/www/user-edit.php (+22/-2) trunk/www/users.php (+127/-14) |
||||
To merge this branch: | bzr merge lp:~vasilev/psiphon/sprint3-579678 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Rod | Needs Fixing | ||
Review via email: mp+34606@code.launchpad.net |
This proposal has been superseded by a proposal from 2010-09-14.
Commit message
Description of the change
Paging, sorting and filtering have been implemented in users.php.
Upd:
Rod's comments on 2010-09-09 have been implemented.
- 124. By Nurlan <ubuntu@ubuntu-desktop>
-
+ Fixes for updated https:/
/bugs.launchpad .net/psiphon/ +bug/579678
* Rod's comments on 2010-09-09 have been implemented.
Robert Vasilev (vasilev) wrote : | # |
Most of your recommendations have been implemented.
In detail:
> 1. Display the total number of users in all cases, even when no filter.
Done.
> 2. Add the paging controls to the top of the page (but also leave at the bottom).
Done.
> 3. Originally when add/edit user and return to list, the new/modified user was
> shown in bold. Now it returns to the default with no filter and page 1. Can
> you make it return to the page with the new user, shown in bold? This isn't
> required: please make a bug to backlog it if it's too much work.
Implemented for editing/disabling case.
For adding case it's quite unobvious to predict on which page new user will appear for different sorting modes.
It will be better to implement highlinghting feature for new users as anover ticket.
> - For code readability, don't mix access control check and input validation
> ...
> Should validate same value used, even if we can reason that it's safe and/or equivalent
Done.
> - Always escape user input in output, even if we don't think it's a risk:
...
> See other files for examples of escaping. We use the helper function escape_html.
Have not found any example of escaping integers with escape_html() in Psiphon code.
Escaping integers with escape_html() not implemented because of unnecessarity.
> - "SELECT COUNT(*) AS cnt FROM user WHERE {$query_where_sql}" is repeated
> twice in a row; could keep result in variable. Even though MySQL caches
> results. If you always display the user total (filter or no) then: 1. always
> get total; 2. get filtered count when filter.
Done. But could not 'keep result in variable' because of '$query_where_sql' may be modified by adding filtering conditions, and results will not be equivalent.
> - Please use a more descriptive variable name: "echo ($pn);"
Done.
Unmerged revisions
Preview Diff
1 | === modified file 'trunk/www/config.php' |
2 | --- trunk/www/config.php 2010-06-28 20:44:20 +0000 |
3 | +++ trunk/www/config.php 2010-09-13 13:18:47 +0000 |
4 | @@ -58,6 +58,7 @@ |
5 | $config[max_login_delay] = 30; // 30 seconds |
6 | |
7 | $config['tickets_ppage'] = 20; |
8 | +$config['users_ppage'] = 100; |
9 | |
10 | $config["sender"] = "psiphonadmin@gmail.com"; |
11 | $config["invitation_sender"] = "psiphonadmin@gmail.com"; |
12 | |
13 | === modified file 'trunk/www/includes/lang.php' |
14 | --- trunk/www/includes/lang.php 2010-09-07 16:38:40 +0000 |
15 | +++ trunk/www/includes/lang.php 2010-09-13 13:18:47 +0000 |
16 | @@ -4630,6 +4630,71 @@ |
17 | Please begin using this URL to log into Psiphon.", |
18 | ), |
19 | |
20 | + 'user_class_filter' => array( |
21 | + 'en' => "User Class Filter", |
22 | + 'fa' => "User Class Filter", |
23 | + 'ru' => "User Class Filter", |
24 | + 'uz' => "User Class Filter", |
25 | + 'tk' => "User Class Filter", |
26 | + 'cn' => "User Class Filter", |
27 | + 'ar' => "User Class Filter", |
28 | + 'vt' => "User Class Filter", |
29 | + 'fr' => "User Class Filter", |
30 | + 'es' => "User Class Filter", |
31 | + ), |
32 | + |
33 | + 'no_filter' => array( |
34 | + 'en' => "No filter", |
35 | + 'fa' => "No filter", |
36 | + 'ru' => "No filter", |
37 | + 'uz' => "No filter", |
38 | + 'tk' => "No filter", |
39 | + 'cn' => "No filter", |
40 | + 'ar' => "No filter", |
41 | + 'vt' => "No filter", |
42 | + 'fr' => "No filter", |
43 | + 'es' => "No filter", |
44 | + ), |
45 | + |
46 | + 'apply_filter' => array( |
47 | + 'en' => "Apply filter", |
48 | + 'fa' => "Apply filter", |
49 | + 'ru' => "Apply filter", |
50 | + 'uz' => "Apply filter", |
51 | + 'tk' => "Apply filter", |
52 | + 'cn' => "Apply filter", |
53 | + 'ar' => "Apply filter", |
54 | + 'vt' => "Apply filter", |
55 | + 'fr' => "Apply filter", |
56 | + 'es' => "Apply filter", |
57 | + ), |
58 | + |
59 | + 'filtered_items_count' => array( |
60 | + 'en' => "Filtered items count", |
61 | + 'fa' => "Filtered items count", |
62 | + 'ru' => "Filtered items count", |
63 | + 'uz' => "Filtered items count", |
64 | + 'tk' => "Filtered items count", |
65 | + 'cn' => "Filtered items count", |
66 | + 'ar' => "Filtered items count", |
67 | + 'vt' => "Filtered items count", |
68 | + 'fr' => "Filtered items count", |
69 | + 'es' => "Filtered items count", |
70 | + ), |
71 | + |
72 | + 'total_users_count' => array( |
73 | + 'en' => "Total users", |
74 | + 'fa' => "Total users", |
75 | + 'ru' => "Total users", |
76 | + 'uz' => "Total users", |
77 | + 'tk' => "Total users", |
78 | + 'cn' => "Total users", |
79 | + 'ar' => "Total users", |
80 | + 'vt' => "Total users", |
81 | + 'fr' => "Total users", |
82 | + 'es' => "Total users", |
83 | + ), |
84 | + |
85 | // DO NOT MODIFY THIS LINE -- END OF TRANSLATIONS |
86 | ); |
87 | |
88 | |
89 | === modified file 'trunk/www/user-edit.php' |
90 | --- trunk/www/user-edit.php 2010-07-16 19:17:57 +0000 |
91 | +++ trunk/www/user-edit.php 2010-09-13 13:18:47 +0000 |
92 | @@ -39,6 +39,25 @@ |
93 | // 403.php exits |
94 | } |
95 | |
96 | +// Chaining paging, filtering, and sorting info back to user list page |
97 | +$_GET['filterby'] = cast_str_to_int($_GET['filterby']); |
98 | +$_GET['ofs'] = cast_str_to_int($_GET['ofs']); |
99 | +$_GET['ord'] = cast_str_to_int($_GET['ord']); |
100 | +$filterby_string = ''; |
101 | +if (isset($_GET['filterby'])) { |
102 | + $filterby_string = "&filterby=" . $_GET['filterby']; |
103 | +} |
104 | +$ord_string=''; |
105 | +if (isset($_GET["ord"])) { |
106 | + $ord_string = "&ord=". $_GET["ord"]; |
107 | +} |
108 | +$ofs_string = ''; |
109 | +if (isset($_GET["ofs"])) { |
110 | + $ofs_string = "&ofs=". $_GET["ofs"]; |
111 | +} |
112 | +$paging_sorting_filtering_params = $ofs_string.$ord_string.$filterby_string; |
113 | + |
114 | + |
115 | // This page has two modes: add and edit. If there are no GET params, then it's |
116 | // in add-mode. If there's an 'user_id' GET param, this it's in edit-mode. (The |
117 | // 'user_id' is the ID of the user to edit.) |
118 | @@ -386,6 +405,7 @@ |
119 | |
120 | $action = getenv("SCRIPT_NAME"); |
121 | if ($user_id !== Null) $action .= "?user_id=$user_id"; |
122 | +$action .= $paging_sorting_filtering_params; |
123 | |
124 | include($_SERVER[DOCUMENT_ROOT]."/header.php"); |
125 | ?> |
126 | @@ -522,9 +542,9 @@ |
127 | <input type="submit" value="<?=msg("submit")?>"/> |
128 | <br/> |
129 | <?if ($submit_success) {?> |
130 | - <a href="users.php?user_id=<?=$user_id?>"><b><?=msg("back_to_users")?></b></a> |
131 | + <a href="users.php?user_id=<?=$user_id.$paging_sorting_filtering_params?>"><b><?=msg("back_to_users")?></b></a> |
132 | <?} else {?> |
133 | - <a href="users.php"><?=msg("cancel")?></a> |
134 | + <a href="users.php?<?=$paging_sorting_filtering_params?>"><?=msg("cancel")?></a> |
135 | <?}?> |
136 | |
137 | <?if ($page_mode == "ADD") {?> |
138 | |
139 | === modified file 'trunk/www/users.php' |
140 | --- trunk/www/users.php 2010-07-05 20:29:49 +0000 |
141 | +++ trunk/www/users.php 2010-09-13 13:18:47 +0000 |
142 | @@ -29,6 +29,8 @@ |
143 | |
144 | $user_edit_page = 'user-edit.php'; |
145 | |
146 | +$column_number_to_field_name = array(1=>'uname', 2=>'email', 3=>'grp', 4=>'current_proxy', 5=>'proxies', 6=>'inv_actv', 7=>'inv_sent'); |
147 | +$default_sort_field = 1; |
148 | // |
149 | // Business logic |
150 | // |
151 | @@ -40,13 +42,25 @@ |
152 | include($_SERVER[DOCUMENT_ROOT]."/http-errors/403.php"); |
153 | // 403.php exits |
154 | } |
155 | - |
156 | + |
157 | // Screen title. Must come before the header.php include. |
158 | |
159 | $title = msg("users_management"); |
160 | |
161 | $_GET['disable'] = cast_str_to_int($_GET['disable']); |
162 | $last_modified_user_id = cast_str_to_int($_GET['user_id']); |
163 | +$_GET['filterby'] = cast_str_to_int($_GET['filterby']); |
164 | +$_GET['ord'] = cast_str_to_int($_GET['ord']); |
165 | +// If 'ord' param is not set or invalid, sort by default column |
166 | +if (!$_GET["ord"] || (abs($_GET["ord"]) > count($column_number_to_field_name))) |
167 | +{ |
168 | + $_GET["ord"] = (int)$default_sort_field; |
169 | +} |
170 | +$orderby_sql = " ORDER BY ".$column_number_to_field_name[abs($_GET["ord"])]; |
171 | +if ($_GET["ord"]<0) |
172 | +{ |
173 | + $orderby_sql.=" DESC"; |
174 | +} |
175 | |
176 | // We need to build up the where clause that will be used in the main query. |
177 | $query_where_sql = get_user_managed_classes_sql_condition($config, $record_user); |
178 | @@ -105,19 +119,118 @@ |
179 | break; |
180 | } |
181 | |
182 | +// count total number of users (unfiltered) |
183 | +$query = "SELECT COUNT(id) AS cnt FROM user WHERE {$query_where_sql}"; |
184 | +$result = db_query_execute($config, $query, convert_null_array($query_where_sql_params)); |
185 | +$record = db_fetch_result($config, $result); |
186 | +$total_users_count = $record["cnt"]; |
187 | +db_free_result($result); |
188 | + |
189 | +$filterby_string = ''; |
190 | +if (isset($_GET['filterby'])) { |
191 | + $filterby_string = "&filterby=" . $_GET['filterby']; |
192 | + |
193 | + // apply user class filter |
194 | + $query_where_sql .= " AND user.grp = :filter_user_class"; |
195 | + $query_where_sql_params[':filter_user_class'] = $_GET['filterby']; |
196 | + $selected_filter = $_GET['filterby']; |
197 | + |
198 | + // count filtered users without LIMITs |
199 | + $query = "SELECT COUNT(id) AS cnt FROM user WHERE {$query_where_sql}"; |
200 | + $result = db_query_execute($config, $query, convert_null_array($query_where_sql_params)); |
201 | + $record = db_fetch_result($config, $result); |
202 | + $filtered_users_count = $record["cnt"]; |
203 | + db_free_result($result); |
204 | +} |
205 | + |
206 | +// pager (from tickets.php)///////////////////////////////////////////////////// |
207 | +$query = "SELECT COUNT(*) AS cnt FROM user WHERE {$query_where_sql}"; |
208 | +$result = db_query_execute($config, $query, convert_null_array($query_where_sql_params)); |
209 | +$record = db_fetch_result($config, $result); |
210 | +$count = $record["cnt"]; |
211 | +db_free_result($result); |
212 | + |
213 | +$per_page = $config['users_ppage']; |
214 | +if (($_GET["ofs"]=(int)$_GET["ofs"])<0) $_GET["ofs"]=0; |
215 | +$last_page=floor(abs($count-0.1)/$per_page); |
216 | +if ($_GET["ofs"]>$last_page) include($_SERVER[DOCUMENT_ROOT]."/http-errors/404.php"); |
217 | +$ord_string=''; |
218 | +if (isset($_GET["ord"])) { |
219 | + $ord_string = "&ord=". $_GET["ord"]; |
220 | +} |
221 | +$ofs_string = ''; |
222 | +if (isset($_GET["ofs"])) { |
223 | + $ofs_string = "&ofs=". $_GET["ofs"]; |
224 | +} |
225 | + |
226 | +$page_numbers_control=''; |
227 | +for ($page=0; $page<=$last_page; $page++) |
228 | +{ |
229 | + if ($page!=$_GET["ofs"]) |
230 | + { |
231 | + $page_numbers_control.="<a href=\"".getenv("SCRIPT_NAME")."?ofs={$page}{$ord_string}{$filterby_string}\">".($page+1)."</a> \n"; |
232 | + } |
233 | + else |
234 | + { |
235 | + $page_numbers_control.="[ <b>".($page+1)."</b> ] \n"; |
236 | + } |
237 | +} |
238 | + |
239 | +$limit_offset = abs((int)($_GET[ofs] * $per_page)); |
240 | +$limit_count = abs((int)$per_page); |
241 | + |
242 | +// end of pager //////////////////////////////////////////////////////////////// |
243 | +$paging_sorting_filtering_params = $ofs_string.$ord_string.$filterby_string; |
244 | + |
245 | +function print_sort_header($col_number, $label, $filterby_string) |
246 | +{ |
247 | + echo "<a href=\"" . getenv("SCRIPT_NAME") . "?ord="; |
248 | + if ($_GET["ord"] == $col_number) |
249 | + echo "-{$col_number}{$filterby_string}\"><b>" . $label . " /\\</b>"; |
250 | + elseif ($_GET["ord"] == -$col_number) |
251 | + echo "{$col_number}{$filterby_string}\"><b>" . $label . " \\/</b>"; |
252 | + else |
253 | + echo "-{$col_number}{$filterby_string}\">" . $label; |
254 | + echo "</a>\n"; |
255 | +} |
256 | + |
257 | include($_SERVER[DOCUMENT_ROOT]."/header.php"); |
258 | ?> |
259 | - |
260 | +<div> |
261 | + <form method="GET" action="<?=getenv("SCRIPT_NAME")?>"> |
262 | + <label><?=msg('user_class_filter') ?>: </label> |
263 | +<? |
264 | + |
265 | +$user_classes = get_user_class_names($config, $record_user, 1, 1); |
266 | + |
267 | +echo_combo_from_array( |
268 | + 'filterby', |
269 | + $user_classes, |
270 | + Null, // no transform function |
271 | + $selected_filter, |
272 | + 1, msg('no_filter')); |
273 | + |
274 | +?> |
275 | +<input type="submit" value="<?=msg("apply_filter") ?>"> |
276 | +<? if (isset($filtered_users_count)) { |
277 | + echo " ".msg('filtered_items_count').": ".$filtered_users_count." "; |
278 | +} |
279 | +echo msg('total_users_count').": ".$total_users_count; |
280 | +?> |
281 | +</form> |
282 | + |
283 | +</div> |
284 | +<? echo $page_numbers_control; ?> |
285 | <table class="brd"> |
286 | <tr> |
287 | - <th><?=msg("name")?></th> |
288 | - <th><?=msg("email")?></th> |
289 | - <th><?=msg("grp")?></th> |
290 | - <th><?=msg("current_proxy")?></th> |
291 | - <th><?=msg("proxies")?></th> |
292 | - <th><?=msg("invites_accepted")?></th> |
293 | - <th><?=msg("invites_sent")?></th> |
294 | - <th><a href="<?=$user_edit_page?>"><?=msg("add_new_user")?></a></th> |
295 | + <th><? print_sort_header(1, msg("name"), $filterby_string) ?></th> |
296 | + <th><? print_sort_header(2, msg("email"), $filterby_string)?></th> |
297 | + <th><? print_sort_header(3, msg("grp"), $filterby_string)?></th> |
298 | + <th><? print_sort_header(4, msg("current_proxy"), $filterby_string)?></th> |
299 | + <th><? print_sort_header(5, msg("proxies"), $filterby_string)?></th> |
300 | + <th><? print_sort_header(6, msg("invites_accepted"), $filterby_string)?></th> |
301 | + <th><? print_sort_header(7, msg("invites_sent"), $filterby_string)?></th> |
302 | + <th><a href="<?=$user_edit_page."?$paging_sorting_filtering_params" ?>"><?=msg("add_new_user")?></a></th> |
303 | </tr> |
304 | |
305 | <? |
306 | @@ -125,7 +238,7 @@ |
307 | $query = "SELECT user.*, ". |
308 | "GROUP_CONCAT(userproxy.proxyid SEPARATOR '".$proxy_separator."') AS proxies ". |
309 | "FROM user LEFT OUTER JOIN userproxy ON user.id = userproxy.userid ". |
310 | - "WHERE {$query_where_sql} GROUP BY uname ORDER BY uname"; |
311 | + "WHERE {$query_where_sql} GROUP BY uname {$orderby_sql} LIMIT {$limit_offset}, {$limit_count}"; |
312 | |
313 | $result = db_query_execute($config, $query, convert_null_array($query_where_sql_params)); |
314 | |
315 | @@ -168,12 +281,12 @@ |
316 | echo "<td>{$_p}{$record['inv_sent']}{$_s}</td>\n"; |
317 | |
318 | echo "<td>{$_p} "; |
319 | - echo "<a href=\"".$user_edit_page."?user_id={$record[id]}\">".msg("edit")."</a> "; |
320 | + echo "<a href=\"".$user_edit_page."?user_id={$record[id]}{$paging_sorting_filtering_params}\">".msg("edit")."</a> "; |
321 | $t=msg($record[locked]?"enable_account":"disable_account"); |
322 | if ($record[id]!=$record_user[id]) |
323 | { |
324 | // The "fresh" GET parameter ensures that the disable link is always coloured as not-visited. |
325 | - echo "<a href=\"".getenv("SCRIPT_NAME")."?disable={$record[id]}&fresh=".bin2hex(microtime())."\" title=\"{$t}\">X</a>"; |
326 | + echo "<a href=\"".getenv("SCRIPT_NAME")."?disable={$record[id]}{$paging_sorting_filtering_params}&fresh=".bin2hex(microtime())."\" title=\"{$t}\">X</a>"; |
327 | } |
328 | else |
329 | { |
330 | @@ -188,7 +301,7 @@ |
331 | ?> |
332 | </table> |
333 | <? |
334 | - |
335 | +echo "<br>\n".$page_numbers_control; |
336 | db_free_result($result); |
337 | |
338 | include($_SERVER[DOCUMENT_ROOT]."/footer.php"); |
This was a team review. Here are our comments:
- After trying the new functionality, we have some minor changes that weren't in the original spec.
1. Display the total number of users in all cases, even when no filter.
2. Add the paging controls to the top of the page (but also leave at the bottom).
3. Originally when add/edit user and return to list, the new/modified user was shown in bold. Now it returns to the default with no filter and page 1. Can you make it return to the page with the new user, shown in bold? This isn't required: please make a bug to backlog it if it's too much work.
- For code readability, don't mix access control check and input validation ("if (!user_ has_permission( $config, $record_user, "accounts") || (isset( $_GET[" ord"]) && (abs($_GET["ord"]) > count($ column_ number_ to_field_ name))) )"
Also, this check won't catch all cases. the logic is mixed in with the later if condition: "if (!$_GET["ord"] || !is_numeric( $_GET[" rod"])) ". It would be easier to review and maintain if the input validation and normalization is in one place.
Should validate same value used, even if we can reason that it's safe and/or equivalent: e.g., abs((int) $_GET[" ord"]) is used vs. abs($_GET["ord"]) is validated.
- Always escape user input in output, even if we don't think it's a risk: "$filterby_string = "&filterby=" . $_GET['filterby'];" and "$ord_string = "&ord=". $_GET["ord"];"
Also when making the page number links below:
if (isset( $_GET[" ord"])) {
$ord_string = "&ord=". $_GET["ord"];
}
See other files for examples of escaping. We use the helper function escape_html.
- "SELECT COUNT(*) AS cnt FROM user WHERE {$query_where_sql}" is repeated twice in a row; could keep result in variable. Even though MySQL caches results. If you always display the user total (filter or no) then: 1. always get total; 2. get filtered count when filter.
- Please use a more descriptive variable name: "echo ($pn);"