/[drupal]/contributions/modules/versioncontrol/versioncontrol.module
ViewVC logotype

Contents of /contributions/modules/versioncontrol/versioncontrol.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.154 - (show annotations) (download) (as text)
Fri Oct 16 19:02:42 2009 UTC (5 weeks, 5 days ago) by marvil07
Branch: MAIN
CVS Tags: HEAD
Changes since 1.153: +1 -39 lines
File MIME type: text/x-php
Avoid own interaction functions to use oop natural way

There is no more reason to keep this two functions:
versioncontrol_backend_implements() and _versioncontrol_call_backend()
was completely replaced with interfaces.

Actually this is the last commit that finish that task.
1 <?php
2 // $Id: versioncontrol.module,v 1.153 2009/10/16 14:15:26 sdboyer Exp $
3 /**
4 * @file
5 * Version Control API - An interface to version control systems
6 * whose functionality is provided by pluggable back-end modules.
7 *
8 * Copyright 2006, 2007 Derek Wright ("dww" , http://drupal.org/user/46549)
9 * Copyright 2007, 2008, 2009 by Jakob Petsovits ("jpetso", http://drupal.org/user/56020)
10 */
11
12 /**
13 * @name backend capabilities
14 * Optional capabilities that backend modules can provide.
15 */
16 //@{
17 define('VERSIONCONTROL_CAPABILITY_ATOMIC_COMMITS', 1);
18 define('VERSIONCONTROL_CAPABILITY_COMMIT_RESTRICTIONS', 2);
19 define('VERSIONCONTROL_CAPABILITY_BRANCH_TAG_RESTRICTIONS', 3);
20 define('VERSIONCONTROL_CAPABILITY_DIRECTORY_REVISIONS', 4);
21 //@}
22
23 /**
24 * @name VCS actions
25 * for a single item (file or directory) in a commit, or for branches and tags.
26 */
27 //@{
28 define('VERSIONCONTROL_ACTION_ADDED', 1);
29 define('VERSIONCONTROL_ACTION_MODIFIED', 2);
30 define('VERSIONCONTROL_ACTION_MOVED', 3); //< == renamed
31 define('VERSIONCONTROL_ACTION_COPIED', 4);
32 define('VERSIONCONTROL_ACTION_MERGED', 5);
33 define('VERSIONCONTROL_ACTION_DELETED', 6);
34 define('VERSIONCONTROL_ACTION_REPLACED', 7);
35 define('VERSIONCONTROL_ACTION_OTHER', 8); //< for example, SVN revprop-only changes
36 //@}
37
38 /**
39 * @name VCS flag for operation item database entries
40 * The flag for operation item database entries, specifying whether an item
41 * is actually a direct "member" item of that operation or just a cached
42 * source item that speeds up operation queries. (Background: we want some
43 * source items to be considered when the caller passes a 'paths' constraint,
44 * but joining those is unnecessarily inefficient. Therefore, searched source
45 * items get directly included in the {versioncontrol_operation_items} table.)
46 * This is an implementation detail, the API user won't get to see this. Ever.
47 */
48 //@{
49 define('VERSIONCONTROL_OPERATION_MEMBER_ITEM', 1);
50 define('VERSIONCONTROL_OPERATION_CACHED_AFFECTED_ITEM', 2);
51 //@}
52
53 /**
54 * @name Constraint 'cardinality' key
55 * Allowed values for the 'cardinality' key in constraint descriptions
56 * provided by hook_versioncontrol_operation_constraint_info().
57 */
58 //@{
59 define('VERSIONCONTROL_CONSTRAINT_MULTIPLE', 1); // default
60 define('VERSIONCONTROL_CONSTRAINT_SINGLE', 2);
61 define('VERSIONCONTROL_CONSTRAINT_SINGLE_OR_MULTIPLE', 3);
62 //@}
63
64 /**
65 * @name User relation constraints
66 * Allowed values for use with the 'user_relation' constraint in
67 * versioncontrol_get_operations() queries.
68 */
69 //@{
70 define('VERSIONCONTROL_USER_ASSOCIATED', 1);
71 define('VERSIONCONTROL_USER_ASSOCIATED_ACTIVE', 2);
72 //@}
73
74 /** Used internally by the repository and account admin pages. Private constant. */
75 define('VERSIONCONTROL_FORM_CREATE', FALSE);
76
77 //TODO: define if we want to do the load each time, per use, or all-in-one like views.inc
78 require_once drupal_get_path('module', 'versioncontrol') .'/includes/VersioncontrolAccount.php';
79 require_once drupal_get_path('module', 'versioncontrol') .'/includes/classes.inc';
80 /**
81 * Implementation of hook_init():
82 * Code that is run on every page request, except for cached ones.
83 */
84 function versioncontrol_init() {
85 // The backend-only part of the API.
86 module_load_include('inc', 'versioncontrol', 'versioncontrol-backend');
87 }
88
89 /**
90 * Implementation of hook_theme().
91 */
92 function versioncontrol_theme() {
93 $theme = array();
94 $theme['versioncontrol_account_username'] = array(
95 'arguments' => array('uid', 'username', 'repository', 'options' => NULL),
96 );
97 $theme['versioncontrol_user_statistics_table'] = array(
98 'arguments' => array('statistics', 'options'),
99 );
100 $theme['versioncontrol_user_statistics_item_list'] = array(
101 'arguments' => array('statistics', 'more_link'),
102 );
103 $theme['versioncontrol_user_statistics_account'] = array(
104 'arguments' => array('user_stats'),
105 );
106 return $theme;
107 }
108
109 /**
110 * Implementation of hook_user():
111 * Register additional user account edit tabs,
112 * and delete VCS accounts when the associated user account is deleted.
113 */
114 function versioncontrol_user($type, &$edit, &$user, $category = NULL) {
115 switch ($type) {
116 case 'categories':
117 $categories = array();
118 $categories[] = array(
119 'name' => 'versioncontrol',
120 // user_menu() pipes 'title' though check_plain() already.
121 'title' => 'Repository accounts',
122 'weight' => 99,
123 );
124 return $categories;
125
126 case 'delete':
127 $accounts = VersioncontrolAccountCache::getInstance()->getAccounts(array('uids' => array($user->uid)), TRUE);
128 if (empty($accounts)) {
129 return;
130 }
131 foreach ($accounts as $uid => $usernames_by_repository) {
132 foreach ($usernames_by_repository as $repo_id => $account) {
133 $account->delete();
134 }
135 }
136 break;
137
138 }
139 }
140
141 /**
142 * Implementation of hook_menu().
143 */
144 function versioncontrol_menu() {
145 $items = array();
146
147 $admin = array(
148 'page callback' => 'drupal_get_form',
149 'access arguments' => array('administer version control systems'),
150 'file' => 'versioncontrol.admin.inc',
151 );
152
153 // If Version Control API is used without the Project module,
154 // we need to define our own version of /admin/project
155 // so the rest of our admin pages all work.
156 if (!module_exists('project')) {
157 $items['admin/project'] = array(
158 'title' => 'Project administration',
159 'description' => 'Administrative interface for project management and related modules.',
160 'position' => 'left',
161 'weight' => 3,
162 'page callback' => 'system_admin_menu_block_page',
163 'access arguments' => array('administer site configuration'),
164 'file' => 'system.admin.inc',
165 'file path' => drupal_get_path('module', 'system'),
166 );
167 }
168
169 $items['admin/project/versioncontrol-settings'] = array(
170 'title' => 'Version control settings',
171 'description' => 'Configure settings for Version Control API and related modules.',
172 'page arguments' => array('versioncontrol_admin_settings'),
173 'type' => MENU_NORMAL_ITEM,
174 ) + $admin;
175 $items['admin/project/versioncontrol-settings/general'] = array(
176 'title' => 'General',
177 'type' => MENU_DEFAULT_LOCAL_TASK,
178 'weight' => -1,
179 );
180
181 $items['admin/project/versioncontrol-repositories'] = array(
182 'title' => 'VCS repositories',
183 'description' => 'Define and configure what version control repositories are connected to your site, and how to integrate each repository with repository browser tools such as ViewVC or WebSVN.',
184 'page arguments' => array('versioncontrol_admin_repository_list'),
185 ) + $admin;
186
187 $weight = 1;
188 $items['admin/project/versioncontrol-repositories/list'] = array(
189 'title' => 'List',
190 'type' => MENU_DEFAULT_LOCAL_TASK,
191 'weight' => $weight,
192 );
193
194 // former !$may_cache
195 /// TODO: Backend specific stuff was done in !$may_cache, as it once
196 /// screwed up after activating a new backend in admin/build/modules.
197 /// Make sure this works now.
198 foreach (versioncontrol_get_backends() as $vcs => $backend) {
199 $items['admin/project/versioncontrol-repositories/add-'. $vcs] = array(
200 'title' => 'Add @vcs repository',
201 'title arguments' => array('@vcs' => $backend->name),
202 'page arguments' => array('versioncontrol_admin_repository_edit',
203 VERSIONCONTROL_FORM_CREATE, $vcs
204 ),
205 'type' => MENU_LOCAL_TASK,
206 'weight' => ++$weight,
207 ) + $admin;
208 }
209 // end former !$may_cache
210
211 $items['admin/project/versioncontrol-repositories/edit/%versioncontrol_repository'] = array(
212 'title' => 'Edit repository',
213 'page arguments' => array('versioncontrol_admin_repository_edit', 4),
214 'type' => MENU_CALLBACK,
215 ) + $admin;
216 $items['admin/project/versioncontrol-repositories/delete/%versioncontrol_repository'] = array(
217 'title' => 'Delete repository',
218 'page arguments' => array('versioncontrol_admin_repository_delete_confirm', 4),
219 'type' => MENU_CALLBACK,
220 ) + $admin;
221
222 $items['admin/project/versioncontrol-accounts'] = array(
223 'title' => 'VCS accounts',
224 'description' => 'Manage associations of Drupal users to VCS user accounts.',
225 'page arguments' => array('versioncontrol_admin_account_list_form'),
226 'type' => MENU_NORMAL_ITEM,
227 ) + $admin;
228 $items['admin/project/versioncontrol-accounts/list'] = array(
229 'title' => 'List',
230 'type' => MENU_DEFAULT_LOCAL_TASK,
231 'weight' => 0,
232 );
233
234 // former !$may_cache
235 /// TODO: Backend specific stuff was done in !$may_cache, as it once
236 /// screwed up after activating a new backend in admin/build/modules.
237 /// Make sure this works now.
238 // TODO (sdb): this should all be reworked using a version of the loader that
239 // takes additional arguments for implementation checking.
240 foreach (versioncontrol_get_backends() as $vcs => $backend) {
241 // we need to check and interface, so create an empty repo here
242 $repo = new $backend->classes['repo'](0, array(), FALSE);
243 if ($repo instanceof VersioncontrolRepositoryImportExport) {
244 $items['admin/project/versioncontrol-accounts/import'] = array(
245 'title' => 'Import',
246 'description' => 'Import an existing set of VCS user accounts.',
247 'page arguments' => array('versioncontrol_admin_account_import_form'),
248 'type' => MENU_LOCAL_TASK,
249 'weight' => 2,
250 ) + $admin;
251 $items['admin/project/versioncontrol-accounts/export'] = array(
252 'title' => 'Export',
253 'description' => 'Export VCS user accounts of a specific repository.',
254 'page arguments' => array('versioncontrol_admin_account_export_form'),
255 'type' => MENU_LOCAL_TASK,
256 'weight' => 3,
257 ) + $admin;
258 $items['admin/project/versioncontrol-accounts/export/%versioncontrol_repository'] = array(
259 'title' => 'Export',
260 'page callback' => 'versioncontrol_admin_account_export_page',
261 'page arguments' => array(4),
262 'type' => MENU_CALLBACK,
263 ) + $admin;
264 }
265 }
266 // end former !$may_cache
267
268 // Account registration and editing pages for the regular user.
269 $items['versioncontrol/register'] = array(
270 'title' => 'Get commit access',
271 'page callback' => 'versioncontrol_account_register_page',
272 'access callback' => TRUE, // access checking is done in the page callback
273 'file' => 'versioncontrol.pages.inc',
274 'type' => MENU_SUGGESTED_ITEM,
275 );
276 $items['user/%versioncontrol_user_accounts/edit/versioncontrol'] = array(
277 // Load with $include_unauthorized == TRUE, so that the user can inspect
278 // his/her VCS accounts even if they are not approved by the admin yet.
279 'load arguments' => array(TRUE),
280 'title callback' => 'versioncontrol_user_accounts_title_callback',
281 'title arguments' => array(1),
282 'page callback' => 'versioncontrol_account_page',
283 'page arguments' => array(1),
284 'access callback' => 'versioncontrol_private_account_access',
285 'access arguments' => array(1),
286 'file' => 'versioncontrol.pages.inc',
287 'weight' => 99,
288 'type' => MENU_LOCAL_TASK,
289 );
290
291 // Autocomplete callback for Drupal usernames that have access to
292 // the repo_id given in arg(3). (No need to fetch the full repository,
293 // as the callback uses a raw & safe database query anyways.)
294 $items['versioncontrol/user/autocomplete'] = array(
295 'title' => 'Version control user autocomplete',
296 'page callback' => 'versioncontrol_user_autocomplete',
297 'access callback' => 'versioncontrol_user_access',
298 'type' => MENU_CALLBACK,
299 );
300
301 return $items;
302 }
303
304 /**
305 * Custom access callback, determining if the current user (or the one given
306 * in @p $account, if set) is permitted to administer version control system
307 * functionality.
308 */
309 function versioncontrol_admin_access($account = NULL) {
310 return user_access('administer version control systems', $account);
311 }
312
313 /**
314 * Custom access callback, determining if the current user (or the one given
315 * in @p $account, if set) is permitted to use version control system
316 * functionality.
317 */
318 function versioncontrol_user_access($account = NULL) {
319 return user_access('use version control systems', $account)
320 || user_access('administer version control systems', $account);
321 }
322
323 /**
324 * Custom access callback, determining if the current user (or the one given
325 * in @p $account, if set) is permitted to view version control account
326 * settings of the user specified the first user id in @p $vcs_accounts.
327 * (We take that parameter because it's what the '%versioncontrol_user_accounts'
328 * wildcard returns.)
329 */
330 function versioncontrol_private_account_access($vcs_accounts, $account = NULL) {
331 $viewed_uid = key($vcs_accounts);
332 if (!$viewed_uid) {
333 return FALSE;
334 }
335 if (is_null($account)) {
336 global $user;
337 $account = clone $user;
338 }
339 return ($viewed_uid == $account->uid && user_access('use version control systems', $account))
340 || user_access('administer version control systems', $account);
341 }
342
343 /**
344 * Title callback for the "user/%versioncontrol_user_accounts/edit/versioncontrol" tab.
345 */
346 function versioncontrol_user_accounts_title_callback($accounts) {
347 $usernames = array();
348 foreach ($accounts as $uid => $user_accounts) {
349 foreach ($user_accounts as $repo_id => $account) {
350 $usernames[] = $account->vcs_username;
351 }
352 }
353 $repositories = VersioncontrolRepositoryCache::getInstance()->getRepositories(array(
354 'repo_ids' => array_keys(reset($accounts)), // a.k.a. list of account repo_ids
355 ));
356 $vcses = array();
357 foreach ($repositories as $repository) {
358 $vcses[$repository['vcs']] = TRUE;
359 }
360 if (count($vcses) == 1) {
361 $repo = array_shift($repositories);
362 return check_plain($repo->backend->name);
363 }
364 return t('Repository accounts');
365 }
366
367 /**
368 * Implementation of hook_perm().
369 */
370 function versioncontrol_perm() {
371 return array(
372 'administer version control systems',
373 'use version control systems',
374 );
375 }
376
377 /**
378 * Menu wildcard loader for repository ids ('%versioncontrol_repository').
379 * Use this only for menu paths - if you want to retrieve a repository with
380 * your own code, use versioncontrol_get_repository() instead.
381 * (Yeah, I know duplicate functions are bad. Hopefully we can sort this out
382 * when repositories are made into real objects, as
383 * versioncontrol_get_repository() will be a static class method then.)
384 */
385 function versioncontrol_repository_load($repo_id) {
386 $repository = VersioncontrolRepositoryCache::getInstance()->getRepository($repo_id);
387 return empty($repository) ? FALSE : $repository;
388 }
389
390 /**
391 * Menu wildcard loader for '%versioncontrol_user_accounts':
392 * Load all VCS accounts of a given user (in the format that
393 * VersioncontrolAccountCache::getInstance()->getAccounts() returns) and return either that
394 * or FALSE if no VCS accounts exist for this user.
395 *
396 * @param $uid
397 * Drupal user id of the user whose VCS accounts should be loaded.
398 * @param $include_unauthorized
399 * Will be passed on to VersioncontrolAccountCache::getInstance()->getAccounts(), see the
400 * API documentation of that function.
401 */
402 function versioncontrol_user_accounts_load($uid, $include_unauthorized = FALSE) {
403 $accounts = VersioncontrolAccountCache::getInstance()->getAccounts(array('uids' => array($uid)), $include_unauthorized);
404 return empty($accounts) ? FALSE : $accounts;
405 }
406
407
408 // API functions start here.
409
410 /**
411 * Get a list of all backends with its detailed information.
412 *
413 * @return
414 * A structured array containing information about all known backends.
415 * Array keys are the unique string identifier of the version control
416 * system.
417 * The corresponding array values are VersioncontrolBackend children
418 * objects.
419 *
420 * If no single backends can be found, an empty array is returned.
421 *
422 * A real-life example of such a result array can be found
423 * in the FakeVCS example module.
424 */
425 function versioncontrol_get_backends() {
426 static $backends;
427
428 if (!isset($backends)) {
429 $backends = module_invoke_all('versioncontrol_backends');
430 }
431 return $backends;
432 }
433
434 /**
435 * Determine all user account authorization methods
436 * (free for all, only admin may create accounts, per-repository approval, ...)
437 * by invoking hook_versioncontrol_authorization_methods().
438 *
439 * @return
440 * A structured array with the unique string identifier of the method as keys
441 * and the user-visible description (wrapped in t()) as values.
442 */
443 function versioncontrol_get_authorization_methods() {
444 static $methods;
445
446 if (!isset($methods)) {
447 $methods = module_invoke_all('versioncontrol_authorization_methods');
448 }
449 return $methods;
450 }
451
452 /**
453 * Implementation of hook_versioncontrol_authorization_methods().
454 *
455 * @return
456 * A structured array containing information about authorization methods
457 * provided by this module, wrapped in a structured array. Array keys are
458 * the unique string identifiers of each authorization method, and
459 * array values are the user-visible method descriptions (wrapped in t()).
460 */
461 function versioncontrol_versioncontrol_authorization_methods() {
462 return array(
463 'versioncontrol_admin' => t('Only administrators can create accounts'),
464 'versioncontrol_none' => t('No approval required'),
465 );
466 }
467
468 function _versioncontrol_get_fallback_authorization_method() {
469 return 'versioncontrol_admin';
470 }
471
472 /**
473 * Assemble a list of query constraints given as string array that's
474 * supposed to be imploded with an SQL "AND", and a $params array containing
475 * the corresponding parameter values for all the '%d' and '%s' placeholders.
476 */
477 function _versioncontrol_construct_repository_constraints($constraints, $backends) {
478 $and_constraints = array();
479 $params = array();
480
481 // Filter out repositories of which the corresponding backend is not enabled,
482 // and handle the 'vcs' constraint at the same time.
483 $placeholders = array();
484 $vcses = array_keys($backends);
485 if (isset($constraints['vcs'])) {
486 $vcses = array_intersect($vcses, $constraints['vcs']);
487 }
488 if (empty($vcses)) {
489 $and_constraints[] = 'FALSE'; // no backends are enabled of those that have been requested
490 }
491 else {
492 foreach ($vcses as $vcs) {
493 $placeholders[] = "'%s'";
494 $params[] = $vcs;
495 }
496 $and_constraints[] = 'r.vcs IN ('. implode(',', $placeholders) .')';
497 }
498
499 if (isset($constraints['repo_ids'])) {
500 if (empty($constraints['repo_ids'])) {
501 $and_constraints[] = 'FALSE';
502 }
503 else {
504 $placeholders = array();
505 foreach ($constraints['repo_ids'] as $repo_id) {
506 $placeholders[] = '%d';
507 $params[] = $repo_id;
508 }
509 $and_constraints[] = 'r.repo_id IN ('. implode(',', $placeholders) .')';
510 }
511 }
512
513 if (isset($constraints['names'])) {
514 if (empty($constraints['names'])) {
515 $and_constraints[] = 'FALSE';
516 }
517 else {
518 $placeholders = array();
519 foreach ($constraints['names'] as $name) {
520 $placeholders[] = "'%s'";
521 $params[] = $name;
522 }
523 $and_constraints[] = 'r.name IN ('. implode(',', $placeholders) .')';
524 }
525 }
526
527 return array($and_constraints, $params);
528 }
529
530 /**
531 * Execute a query with either db_query(), db_query_range() or pager_query().
532 * Which one of those is called, and with which parameters, is specified by the
533 * @p $options array, see versioncontrol_get_operations() for a description of
534 * possible array keys and option values.
535 */
536 function _versioncontrol_query($query, $params, $options) {
537 if (isset($options['query_type']) && $options['query_type'] == 'pager') {
538 $element = isset($options['pager_element']) ? $options['pager_element'] : 0;
539 return pager_query($query, $options['count'], $element, NULL, $params);
540 }
541 elseif (isset($options['query_type']) && $options['query_type'] == 'range') {
542 return db_query_range($query, $params, $options['from'], $options['count']);
543 }
544 else {
545 return db_query($query, $params);
546 }
547 }
548
549 /**
550 * Implementation of hook_versioncontrol_operation_constraint_info().
551 */
552 function versioncontrol_versioncontrol_operation_constraint_info() {
553 return array(
554 'vcs' => array('join callback' => 'versioncontrol_table_repositories_join'),
555 'vc_op_ids' => array(),
556 'revisions' => array(),
557 'repo_ids' => array('join callback' => 'versioncontrol_table_repositories_join'),
558 'date_lower' => array('cardinality' => VERSIONCONTROL_CONSTRAINT_SINGLE),
559 'date_upper' => array('cardinality' => VERSIONCONTROL_CONSTRAINT_SINGLE),
560 'uids' => array(),
561 'usernames' => array(),
562 'user_relation' => array('cardinality' => VERSIONCONTROL_CONSTRAINT_SINGLE),
563 'message' => array('cardinality' => VERSIONCONTROL_CONSTRAINT_SINGLE_OR_MULTIPLE),
564 'item_revision_ids' => array('join callback' => 'versioncontrol_table_operation_items_join'),
565 'item_revisions' => array('join callback' => 'versioncontrol_table_item_revisions_join'),
566 'paths' => array('join callback' => 'versioncontrol_table_item_revisions_join'),
567 'labels' => array('join callback' => 'versioncontrol_table_labels_join'),
568 'tags' => array('join callback' => 'versioncontrol_table_labels_join'),
569 'branches' => array('join callback' => 'versioncontrol_table_labels_join'),
570 'types' => array(),
571 );
572 }
573
574 /**
575 * Implementation of hook_versioncontrol_operation_constraints_alter():
576 * Include only operations for enabled backends in the query.
577 */
578 function versioncontrol_versioncontrol_operation_constraints_alter($constraints) {
579 // Filter out entries of which the corresponding backend is not enabled,
580 // and handle the 'vcs' constraint at the same time.
581 $backends = versioncontrol_get_backends();
582 $vcses = array_keys($backends);
583
584 if (isset($constraints['vcs'])) {
585 $vcses = array_intersect($vcses, $constraints['vcs']);
586 }
587 $constraints['vcs'] = $vcses;
588 }
589
590 /**
591 * Filter operations by their associated backend.
592 */
593 function versioncontrol_operation_constraint_vcs($constraint, &$tables, &$and_constraints, &$params) {
594 $placeholders = array();
595 foreach ($constraint as $vcs) {
596 $placeholders[] = "'%s'";
597 $params[] = $vcs;
598 }
599 $and_constraints[] = $tables['versioncontrol_repositories']['alias'] .'.vcs
600 IN ('. implode(',', $placeholders) .')';
601 }
602
603 /**
604 * Filter operations by their version control operation id.
605 */
606 function versioncontrol_operation_constraint_vc_op_ids($constraint, &$tables, &$and_constraints, &$params) {
607 $placeholders = array();
608 foreach ($constraint as $vc_op_id) {
609 $placeholders[] = '%d';
610 $params[] = $vc_op_id;
611 }
612 $and_constraints[] = $tables['versioncontrol_operations']['alias'] .'.vc_op_id
613 IN ('. implode(',', $placeholders) .')';
614 }
615
616 /**
617 * Filter operations by their revision identifier.
618 */
619 function versioncontrol_operation_constraint_revisions($constraint, &$tables, &$and_constraints, &$params) {
620 $placeholders = array();
621 foreach ($constraint as $revision) {
622 $placeholders[] = "'%s'";
623 $params[] = $revision;
624 }
625 $and_constraints[] = $tables['versioncontrol_operations']['alias'] .'.revision
626 IN ('. implode(',', $placeholders) .')';
627 }
628
629 /**
630 * Filter operations by their repository id.
631 */
632 function versioncontrol_operation_constraint_repo_ids($constraint, &$tables, &$and_constraints, &$params) {
633 $placeholders = array();
634 foreach ($constraint as $repo_id) {
635 $placeholders[] = '%d';
636 $params[] = $repo_id;
637 }
638 $and_constraints[] = $tables['versioncontrol_repositories']['alias'] .'.repo_id
639 IN ('. implode(',', $placeholders) .')';
640 }
641
642 /**
643 * Filter operations by a lower date bound.
644 */
645 function versioncontrol_operation_constraint_date_lower($constraint, &$tables, &$and_constraints, &$params) {
646 $and_constraints[] = '('. $tables['versioncontrol_operations']['alias'] .'.date >= %d)';
647 $params[] = $constraint;
648 }
649
650 /**
651 * Filter operations by an upper date bound.
652 */
653 function versioncontrol_operation_constraint_date_upper($constraint, &$tables, &$and_constraints, &$params) {
654 $and_constraints[] = '('. $tables['versioncontrol_operations']['alias'] .'.date <= %d)';
655 $params[] = $constraint;
656 }
657
658 /**
659 * Filter operations by their associated Drupal user id.
660 */
661 function versioncontrol_operation_constraint_uids($constraint, &$tables, &$and_constraints, &$params) {
662 $placeholders = array();
663 foreach ($constraint as $uid) {
664 $placeholders[] = '%d';
665 $params[] = $uid;
666 }
667 $and_constraints[] = $tables['versioncontrol_operations']['alias'] .'.uid
668 IN ('. implode(',', $placeholders) .')';
669 }
670
671 /**
672 * Filter operations by the VCS username of the operation author.
673 */
674 function versioncontrol_operation_constraint_usernames($constraint, &$tables, &$and_constraints, &$params) {
675 $placeholders = array();
676 foreach ($constraint as $username) {
677 $placeholders[] = "'%s'";
678 $params[] = $username;
679 }
680 $and_constraints[] = $tables['versioncontrol_operations']['alias'] .'.username
681 IN ('. implode(',', $placeholders) .')';
682 }
683
684 /**
685 * Filter operations by associated Drupal user status of the operation author.
686 */
687 function versioncontrol_operation_constraint_user_relation($constraint, &$tables, &$and_constraints, &$params) {
688 $and_constraints[] = $tables['versioncontrol_operations']['alias'] .'.uid <> 0';
689
690 if ($constraint == VERSIONCONTROL_USER_ASSOCIATED_ACTIVE) {
691 versioncontrol_table_users_join($tables);
692 $and_constraints[] = $tables['users']['alias'] .'.status = 1';
693 }
694 }
695
696 /**
697 * Filter operations by their log message.
698 */
699 function versioncontrol_operation_constraint_message($constraint, &$tables, &$and_constraints, &$params) {
700 $or_constraints = array();
701 foreach ($constraint as $message_part) {
702 $or_constraints[] = $tables['versioncontrol_operations']['alias'] .".message LIKE '%s'";
703 $params[] = '%'. $message_part .'%';
704 }
705 $and_constraints[] = '('. implode(' OR ', $or_constraints) .')';
706 }
707
708 /**
709 * Filter operations by item_revision_ids of associated items.
710 */
711 function versioncontrol_operation_constraint_item_revision_ids($constraint, &$tables, &$and_constraints, &$params) {
712 $or_constraints = array();
713
714 $placeholders = array();
715 foreach ($constraint as $item_revision_id) {
716 $placeholders[] = '%d';
717 $params[] = $item_revision_id;
718 }
719 $and_constraints[] = $tables['versioncontrol_operation_items']['alias'] .'.item_revision_id
720 IN ('. implode(',', $placeholders) .')';
721
722 // Exact search for target items, no fuzzy source item path results.
723 $params[] = VERSIONCONTROL_OPERATION_MEMBER_ITEM;
724 $and_constraints[] = $tables['versioncontrol_operation_items']['alias'] .'.type = %d';
725 }
726
727 /**
728 * Filter operations by revision identifiers of associated items.
729 */
730 function versioncontrol_operation_constraint_item_revisions($constraint, &$tables, &$and_constraints, &$params) {
731 $or_constraints = array();
732
733 $placeholders = array();
734 foreach ($constraint as $revision) {
735 $placeholders[] = "'%s'";
736 $params[] = $revision;
737 }
738 $and_constraints[] = $tables['versioncontrol_item_revisions']['alias'] .'.revision IN ('. implode(',', $placeholders) .')';
739
740 // Exact search for target items, no fuzzy source item path results.
741 $params[] = VERSIONCONTROL_OPERATION_MEMBER_ITEM;
742 $and_constraints[] = $tables['versioncontrol_operation_items']['alias'] .'.type = %d';
743 }
744
745 /**
746 * Filter operations by item paths of associated items.
747 */
748 function versioncontrol_operation_constraint_paths($constraint, &$tables, &$and_constraints, &$params) {
749 $or_constraints = array();
750
751 foreach ($constraint as $path) {
752 $current_path = $path;
753 $placeholders = array();
754
755 // Both the given path and all its parent directories are included in the query.
756 while (TRUE) {
757 $placeholders[] = "'%s'";
758 $params[] = $current_path;
759
760 if ($current_path == dirname($current_path)) {
761 break; // we reached the root directory, '/'
762 }
763 $current_path = dirname($current_path);
764 }
765 $or_constraints[] = $tables['versioncontrol_item_revisions']['alias'] .'.path IN ('. implode(',', $placeholders) .')';
766
767 // Also include any children paths of the given one
768 // (will only yield results for directory paths).
769 $or_constraints[] = $tables['versioncontrol_item_revisions']['alias'] .".path LIKE '%s'";
770 $params[] = $path . (($path[strlen($path)-1] == '/') ? '%' : '/%');
771 }
772 $and_constraints[] = '('. implode(' OR ', $or_constraints) .')';
773 }
774
775 /**
776 * Filter operations by associated label names (both branches and tags).
777 */
778 function versioncontrol_operation_constraint_labels($constraint, &$tables, &$and_constraints, &$params) {
779 $or_constraints = array();
780 foreach ($constraint as $label_name) {
781 $or_constraints[] = $tables['versioncontrol_labels']['alias'] .".name = '%s'";
782 $params[] = $label_name;
783 }
784 $and_constraints[] = '('. implode(' OR ', $or_constraints) .')';
785 }
786
787 /**
788 * Filter operations by associated tag names.
789 */
790 function versioncontrol_operation_constraint_tags($constraint, &$tables, &$and_constraints, &$params) {
791 $or_constraints = array();
792 foreach ($constraint as $label_name) {
793 $or_constraints[] = $tables['versioncontrol_labels']['alias'] .".name = '%s'";
794 $params[] = $label_name;
795 }
796 $and_constraints[] = '(('. implode(' OR ', $or_constraints) .') AND '.
797 $tables['versioncontrol_labels']['alias'] .'.type = %d)';
798 $params[] = VERSIONCONTROL_OPERATION_TAG;
799 }
800
801 /**
802 * Filter operations by associated branch names.
803 * (Applies to both branch operations and commits).
804 */
805 function versioncontrol_operation_constraint_branches($constraint, &$tables, &$and_constraints, &$params) {
806 $or_constraints = array();
807 foreach ($constraint as $label_name) {
808 $or_constraints[] = $tables['versioncontrol_labels']['alias'] .".name = '%s'";
809 $params[] = $label_name;
810 }
811 $and_constraints[] = '(('. implode(' OR ', $or_constraints) .') AND '.
812 $tables['versioncontrol_labels']['alias'] .'.type = %d)';
813 $params[] = VERSIONCONTROL_OPERATION_BRANCH;
814 }
815
816 /**
817 * Filter operations by their operation type. As noted in the API documentation
818 * for versioncontrol_get_operations(), this filter is kinda smart and also
819 * includes commits for version control systems like Subversion when a branch
820 * or tag is affected by that commit.
821 */
822 function versioncontrol_operation_constraint_types($constraint, &$tables, &$and_constraints, &$params) {
823 $or_constraints = array();
824
825 if (in_array(VERSIONCONTROL_OPERATION_COMMIT, $constraint)) {
826 $or_constraints[] = $tables['versioncontrol_operations']['alias'] .'.type = %d';
827 $params[] = VERSIONCONTROL_OPERATION_COMMIT;
828 }
829 if (in_array(VERSIONCONTROL_OPERATION_BRANCH, $constraint)) {
830 versioncontrol_table_labels_join($tables);
831 $or_constraints[] = '('.
832 $tables['versioncontrol_labels']['alias'] .'.type = %d AND '.
833 $tables['versioncontrol_operation_labels']['alias'] .'.action <> %d
834 )';
835 $params[] = VERSIONCONTROL_LABEL_BRANCH; // label.type
836 // oplabel.action is != normal commits, we don't want those:
837 $params[] = VERSIONCONTROL_ACTION_MODIFIED;
838 }
839 if (in_array(VERSIONCONTROL_OPERATION_TAG, $constraint)) {
840 versioncontrol_table_labels_join($tables);
841 $or_constraints[] = '('.
842 $tables['versioncontrol_labels']['alias'] .'.type = %d AND '.
843 $tables['versioncontrol_operation_labels']['alias'] .'.action <> %d
844 )';
845 $params[] = VERSIONCONTROL_OPERATION_TAG; // label.type
846 // oplabel.action is != normal commits, we don't want those:
847 $params[] = VERSIONCONTROL_ACTION_MODIFIED;
848 }
849 $and_constraints[] = '('. implode(' OR ', $or_constraints) .')';
850 }
851
852 /**
853 * Take an existing @p $tables array and add the table join for
854 * {versioncontrol_repositories}. Only meant to be used within a
855 * constraint construction callback.
856 */
857 function versioncontrol_table_repositories_join(&$tables) {
858 if (!isset($tables['versioncontrol_repositories'])) {
859 $tables['versioncontrol_repositories'] = array(
860 'alias' => 'r',
861 'join_on' => 'op.repo_id = r.repo_id',
862 );
863 }
864 }
865
866 /**
867 * Take an existing @p $tables array and add the table join for {users} on
868 * {versioncontrol_operations}.uid. Only meant to be used within a
869 * constraint construction callback.
870 */
871 function versioncontrol_table_users_join(&$tables) {
872 if (!isset($tables['users'])) {
873 $tables['users'] = array(
874 'alias' => 'user',
875 'join_on' => 'op.uid = user.uid',
876 );
877 }
878 }
879
880 /**
881 * Take an existing @p $tables array and add the table joins for
882 * {versioncontrol_operation_labels} and {versioncontrol_labels}.
883 * Only meant to be used within a constraint construction callback.
884 */
885 function versioncontrol_table_labels_join(&$tables) {
886 if (!isset($tables['versioncontrol_operation_labels'])) {
887 $tables['versioncontrol_operation_labels'] = array(
888 'alias' => 'oplabel',
889 'join_on' => $tables['versioncontrol_operations']['alias'] .'.vc_op_id = oplabel.vc_op_id',
890 );
891 $tables['versioncontrol_labels'] = array(
892 'alias' => 'label',
893 'join_on' => 'oplabel.label_id = label.label_id',
894 );
895 }
896 }
897
898 /**
899 * Take an existing @p $tables array and add the table join for
900 * {versioncontrol_operation_items}.
901 * Only meant to be used within a constraint construction callback.
902 */
903 function versioncontrol_table_operation_items_join(&$tables) {
904 if (!isset($tables['versioncontrol_operation_items'])) {
905 $tables['versioncontrol_operation_items'] = array(
906 'alias' => 'opitem',
907 'join_on' => $tables['versioncontrol_operations']['alias'] .'.vc_op_id = opitem.vc_op_id',
908 );
909 }
910 }
911
912 /**
913 * Take an existing @p $tables array and add the table joins for
914 * {versioncontrol_operation_labels} and {versioncontrol_labels}.
915 * Only meant to be used within a constraint construction callback.
916 */
917 function versioncontrol_table_item_revisions_join(&$tables) {
918 if (!isset($tables['versioncontrol_item_revisions'])) {
919 versioncontrol_table_operation_items_join($tables);
920
921 $tables['versioncontrol_item_revisions'] = array(
922 'alias' => 'ir',
923 'join_on' => 'opitem.item_revision_id = ir.item_revision_id',
924 );
925 }
926 }
927
928 /**
929 * Return a subset of the given operations according to the
930 * $page and $limit values.
931 */
932 function _versioncontrol_page_operations($operations, $page, $limit) {
933 $i = 0;
934 $paged_operations = array();
935 foreach ($operations as $operation) {
936 if ($i >= ($page * $limit) && $i < (($page+1) * $limit)) {
937 $paged_operations[] = $operation;
938 }
939 ++$i;
940 }
941 return $paged_operations;
942 }
943
944
945 /**
946 * Retrieve additional information about the origin of a given set of items.
947 *
948 * @param $repository
949 * The repository that the items are located in.
950 * @param $items
951 * An array of item arrays, for example as returned by
952 * VersioncontrolOperation::getItems().
953 *
954 * @return
955 * This function does not have a return value; instead, it alters the
956 * given item arrays and adds additional information about their origin.
957 * The following elements will be set for all items whose source items
958 * could be retrieved.
959 *
960 * - 'action': Specifies how the item was changed.
961 * One of the predefined VERSIONCONTROL_ACTION_* values.
962 * - 'source_items': An array with the previous revision(s) of the affected
963 * item. Empty if 'action' is VERSIONCONTROL_ACTION_ADDED. The key for
964 * all items in this array is the respective item path.
965 * - 'replaced_item': The previous but technically unrelated item at the
966 * same location as the current item. Only exists if this previous item
967 * was deleted and replaced by a different one that was just moved
968 * or copied to this location.
969 * - 'line_changes': Only exists if line changes have been recorded for this
970 * action - if so, this is an array containing the number of added lines
971 * in an element with key 'added', and the number of removed lines in
972 * the 'removed' key.
973 */
974 function versioncontrol_fetch_source_items($repository, &$items) {
975 if (empty($items)) {
976 return;
977 }
978 $placeholders = array();
979 $ids = array();
980 $item_keys = array();
981
982 foreach ($items as $key => $item) {
983 // If we don't yet know the item_revision_id (required for db queries), try
984 // to retrieve it. If we don't find it, we can't fetch this item's sources.
985 if ($item->fetchItemRevisionId()) {
986 $placeholders[] = '%d';
987 $ids[] = $item->item_revision_id;
988 $item_keys[$item->item_revision_id] = $key;
989 }
990 }
991 if (empty($ids)) {
992 return;
993 }
994
995 $result = db_query(
996 'SELECT sit.item_revision_id, sit.source_item_revision_id, sit.action,
997 sit.line_changes_recorded, sit.line_changes_added,
998 sit.line_changes_removed, ir.path, ir.revision, ir.type
999 FROM {versioncontrol_source_items} sit
1000 LEFT JOIN {versioncontrol_item_revisions} ir
1001 ON sit.source_item_revision_id = ir.item_revision_id
1002 WHERE sit.item_revision_id IN ('. implode(',', $placeholders) .')', $ids);
1003
1004 while ($item_revision = db_fetch_object($result)) {
1005 $successor_key = $item_keys[$item_revision->item_revision_id];
1006 if (!isset($items[$successor_key]->source_items)) {
1007 $items[$successor_key]->source_items = array();
1008 }
1009
1010 $item = new $repository->backend->classes['item'](
1011 $item_revision->type,
1012 $item_revision->path,
1013 $item_revision->revision,
1014 NULL,
1015 $repository
1016 );
1017 $item->selected_label = new stdClass();
1018 $item->selected_label->get_from = 'other_item';
1019 $item->selected_label->other_item = &$items[$successor_key];
1020 $item->selected_label->other_item_tags = array('successor_item');
1021
1022 // Insert the item and its associated action into the successor item.
1023 if ($item_revision->action == VERSIONCONTROL_ACTION_REPLACED) {
1024 $items[$successor_key]->replaced_item = $item;
1025 }
1026 elseif ($item_revision->action == VERSIONCONTROL_ACTION_ADDED) {
1027 $items[$successor_key]['action'] = $item_revision->action;
1028 // Added items only join to an empty (NULL) item, ignore that one
1029 // instead of adding it to the source items.
1030 }
1031 else {
1032 $items[$successor_key]->action = $item_revision->action;
1033 $items[$successor_key]->source_items[$item->path] = $item;
1034 }
1035
1036 // Add the lines-changed information if it has been recorded.
1037 // Only a single source item entry should hold this information,
1038 // so no emphasis is placed on merging it across multiple source items.
1039 if ($item_revision->line_changes_recorded) {
1040 $items[$successor_key]->line_changes = array(
1041 'added' => $item_revision->line_changes_added,
1042 'removed' => $item_revision->line_changes_removed,
1043 );
1044 }
1045 }
1046 }
1047
1048 /**
1049 * Retrieve additional information about the successors of a given set
1050 * of items.
1051 *
1052 * @param $repository
1053 * The repository that the items are located in.
1054 * @param $items
1055 * An array of item arrays, for example as returned by
1056 * VersioncontrolOperation::getItems().
1057 *
1058 * @return
1059 * This function does not have a return value; instead, it alters the
1060 * given item arrays and adds additional information about their successors.
1061 * The following elements will be set for all items whose successor items
1062 * could be retrieved.
1063 *
1064 * - 'successor_items': An array with the previous revision(s) of the
1065 * affected item. The key for all items in this array is the respective
1066 * item path, and all of these items will have the 'actions' and
1067 * 'source_items' properties (as documented by
1068 * versioncontrol_fetch_source_items()) filled in.
1069 * - 'replaced_by_item': The succeeding but technically unrelated item at the
1070 * same location as the current item. Only exists if the original item
1071 * was deleted and replaced by a the succeeding one that was just moved
1072 * or copied to this location.
1073 */
1074 function versioncontrol_fetch_successor_items($repository, &$items) {
1075 if (empty($items)) {
1076 return;
1077 }
1078 $placeholders = array();
1079 $ids = array();
1080 $item_keys = array();
1081
1082 foreach ($items as $key => $item) {
1083 // If we don't yet know the item_revision_id (required for db queries), try
1084 // to retrieve it. If we don't find it, we can't fetch this item's sources.
1085 if ($item->fetchItemRevisionId()) {
1086 $placeholders[] = '%d';
1087 $ids[] = $item->item_revision_id;
1088 $item_keys[$item->item_revision_id] = $key;
1089 }
1090 }
1091
1092 $result = db_query(
1093 'SELECT sit.item_revision_id, sit.source_item_revision_id, sit.action,
1094 ir.path, ir.revision, ir.type
1095 FROM {versioncontrol_source_items} sit
1096 INNER JOIN {versioncontrol_item_revisions} ir
1097 ON sit.item_revision_id = ir.item_revision_id
1098 WHERE sit.source_item_revision_id IN ('. implode(',', $placeholders) .')', $ids);
1099
1100 while ($item_revision = db_fetch_object($result)) {
1101 $source_key = $item_keys[$item_revision->source_item_revision_id];
1102 if (!isset($items[$source_key]['successor_items'])) {
1103 $items[$source_key]['successor_items'] = array();
1104 }
1105
1106 $item = array(
1107 'path' => $item_revision->path,
1108 'revision' => $item_revision->revision,
1109 'type' => $item_revision->type,
1110 'item_revision_id' => $item_revision->item_revision_id,
1111 );
1112 $item['selected_label'] = new stdClass();
1113 $item['selected_label']->get_from = 'other_item';
1114 $item['selected_label']->other_item = &$items[$source_key];
1115 $item['selected_label']->other_item_tags = array('source_item');
1116
1117 // Insert the item and its associated action into the source item.
1118 if ($item_revision->action == VERSIONCONTROL_ACTION_REPLACED) {
1119 $items[$source_key]['replaced_by_item'] = $item;
1120 }
1121 else {
1122 if ($item_revision->action == VERSIONCONTROL_ACTION_MERGED) {
1123 // If we've got a merge action then there are multiple source items,
1124 // the one that we know is not sufficient. (And of course, we won't
1125 // return an item with an incomplete 'source_items' property.)
1126 // So let's retrieve all of those source items.
1127 $successor_items = array($item['path'] => $item);
1128 versioncontrol_fetch_source_items($repository, $successor_items);
1129 $item = $successor_items[$item['path']];
1130 }
1131 else { // No "merged" action: the original item is the only source item.
1132 $item['action'] = $item_revision->action;
1133 $item['source_items'] = array(
1134 $items[$source_key]['path'] => $items[$source_key],
1135 );
1136 }
1137 $items[$source_key]['successor_items'][$item['path']] = $item;
1138 }
1139 }
1140 }
1141
1142 /**
1143 * Retrieve the commit operation corresponding to each item in a list of items.
1144 *
1145 * @param $repository
1146 * The repository that the items are located in.
1147 * @param $items
1148 * An array of item arrays, for example as returned by
1149 * VersioncontrolOperation::getItems().
1150 *
1151 * @return
1152 * This function does not have a return value; instead, it alters the
1153 * given item arrays and adds additional information about their
1154 * corresponding commit operation in an 'commit_operation' property.
1155 * If no corresponding commit was found, this property will not be set.
1156 */
1157 function versioncontrol_fetch_item_commit_operations($repository, &$items) {
1158 $placeholders = array();
1159 $ids = array();
1160 $item_keys = array();
1161
1162 $fetch_by_revision_id = FALSE;
1163
1164 // If there are atomic commits and versioned directories (= SVN), we'll miss
1165 // out on operations for directory items if those are not (always) captured
1166 // in the {versioncontrol_operation_items} table.
1167 // So fetch by revision id instead in that case.
1168 if (in_array(VERSIONCONTROL_CAPABILITY_ATOMIC_COMMITS, $repository->backend->capabilities)) {
1169 $fetch_by_revision_id = TRUE;
1170 }
1171
1172 foreach ($items as $key => $item) {
1173 if (!empty($item['commit_operation'])) {
1174 continue; // No need to insert an operation if it's already there.
1175 }
1176 if ($fetch_by_revision_id && !empty($item['revision'])) {
1177 $ids[$item['revision']] = TRUE; // automatic duplicate elimination
1178 }
1179 // If we don't yet know the item_revision_id (required for db queries), try
1180 // to retrieve it. If we don't find it, we can't fetch this item's sources.
1181 if ($item->fetchItemRevisionId()) {
1182 $placeholders[] = '%d';
1183 $ids[] = $item->item_revision_id;
1184 $item_keys[$item->item_revision_id] = $key;
1185 }
1186 }
1187 if (empty($ids)) {
1188 return;
1189 }
1190
1191 if ($fetch_by_revision_id) {
1192 $commit_operations = VersioncontrolOperationCache::getInstance()->getCommits(array(
1193 'repo_ids' => array($repository['repo_id']),
1194 'revisions' => array_keys($ids),
1195 ));
1196 // Associate the commit operations to the items.
1197 foreach ($items as $key => $item) {
1198 foreach ($commit_operations as $commit_operation) {
1199 if ($item['revision'] == $commit_operation['revision']) {
1200 $items[$key]['commit_operation'] = $commit_operation;
1201 }
1202 }
1203 }
1204 }
1205 else { // fetch by operation/item association
1206 $result = db_query(
1207 'SELECT item_revision_id, vc_op_id
1208 FROM {versioncontrol_operation_items}
1209 WHERE item_revision_id IN ('. implode(',', $placeholders) .')', $ids);
1210
1211 $operation_item_mapping = array();
1212 while ($opitem = db_fetch_object($result)) {
1213 $operation_item_mapping[$opitem->vc_op_id][] = $opitem->item_revision_id;
1214 }
1215 $commit_operations = VersioncontrolOperationCache::getInstance()->getCommits(array(
1216 'vc_op_ids' => array_keys($operation_item_mapping),
1217 ));
1218
1219 // Associate the commit operations to the items.
1220 foreach ($commit_operations as $commit_operation) {
1221 $item_revision_ids = $operation_item_mapping[$commit_operation['vc_op_id']];
1222