/[drupal]/contributions/modules/cvslog/cvs.module
ViewVC logotype

Contents of /contributions/modules/cvslog/cvs.module

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


Revision 1.234 - (show annotations) (download) (as text)
Mon Sep 14 22:09:28 2009 UTC (2 months, 1 week ago) by damz
Branch: MAIN
CVS Tags: DRUPAL-6--1-0-ALPHA3, HEAD
Changes since 1.233: +2 -2 lines
File MIME type: text/x-php
Stop gap solution to #577424: make sure to validate the node even if the title is '0'.
1 <?php
2 // $Id: cvs.module,v 1.233 2009/08/05 20:27:56 dww Exp $
3 // $Name: $
4
5 /**
6 * @file
7 * Provides access to CVS commit logs.
8 *
9 * @internal
10 * commit 139 looks weird. Remove from both cvs_files and cvs_messages?
11 * some commits are done by user 'root' ?
12 */
13
14 // Status values for CVS accounts. Update _cvs_get_status_options() for any changes.
15 define('CVS_PENDING', 0);
16 define('CVS_APPROVED', 1);
17 define('CVS_DECLINED', 2);
18 define('CVS_QUEUED', 3);
19 define('CVS_DISABLED', 4);
20 define('CVS_MIN_PASS_LENGTH', 5);
21 define('CVS_LICENSE', 'GNU public license, version 2');
22 define('CVS_LICENSE_LINK', 'http://www.gnu.org/licenses/old-licenses/gpl-2.0.html');
23
24 if (function_exists('drupal_get_path')) {
25 $path = drupal_get_path('module', 'cvs');
26 if (file_exists("$path/cvs_local.inc")) {
27 require_once "$path/cvs_local.inc";
28 }
29 }
30
31 /**
32 * Implementation of hook_help().
33 */
34 function cvs_help($path, $arg) {
35 switch ($path) {
36 case 'cvs':
37 case 'admin/project/cvs-accounts':
38 case 'admin/project/cvs-repositories':
39 case 'admin/project/cvs-settings':
40 return t('CVS (Concurrent Versions System) is a code management system used by developers to collaborate and track modifications of code.');
41 }
42 }
43
44 /**
45 * Implementation of hook_menu().
46 */
47 function cvs_menu() {
48 global $base_url;
49 $items = array();
50
51 // User pages:
52 $access = 'access CVS messages';
53 $admin_access = 'administer CVS';
54
55 $items['cvs'] = array(
56 'title' => 'CVS messages',
57 'page callback' => 'cvs_page',
58 'access arguments' => array($access),
59 'type' => MENU_SUGGESTED_ITEM,
60 );
61 $items['cvs-application'] = array(
62 'title' => 'CVS application form',
63 'page callback' => 'cvs_application_page',
64 'access arguments' => array($access),
65 'type' => MENU_SUGGESTED_ITEM,
66 );
67
68 // autocomplete callback
69 $items['cvs/user/autocomplete'] = array(
70 'title' => 'CVS user autocomplete',
71 'page callback' => 'cvs_user_autocomplete',
72 'access arguments' => array($access),
73 'type' => MENU_CALLBACK,
74 );
75
76 // Admin pages:
77
78 // If for some reason someone is using this module without
79 // project.module (which is technically legitimate, though hard to
80 // imagine), we need to define our own version of /admin/project
81 // so the rest of our admin pages all work.
82 if (!module_exists('project')) {
83 $items['admin/project'] = array(
84 'title' => 'Project administration',
85 'description' => 'Administrative interface for project management and related modules.',
86 'page callback' => 'system_admin_menu_block_page',
87 'file' => 'system.admin.inc',
88 'file path' => drupal_get_path('module', 'system'),
89 'access arguments' => array($admin_access),
90 'type' => MENU_NORMAL_ITEM,
91 );
92 }
93
94 $items['admin/project/cvs-settings'] = array(
95 'description' => 'Modify CVS management settings, such as how it integrates with projects on your site, the text used on the CVS account application form, and the default wording for emails sent when CVS accounts are approved or denied.',
96 'title' => 'CVS settings',
97 'page callback' => 'drupal_get_form',
98 'page arguments' => array('cvs_settings_form'),
99 'access arguments' => array($admin_access),
100 'type' => MENU_NORMAL_ITEM,
101 );
102
103 // Accounts:
104 $items['admin/project/cvs-accounts'] = array(
105 'title' => 'CVS accounts',
106 'description' => 'Approve or deny requests from the <a href="'. $base_url .'/cvs-application">CVS application</a>, import CVS users from an existing CVSROOT/password file, or export a CVSROOT/passwd file to install in any of your CVS repositories.',
107 'page callback' => 'cvs_account',
108 'access arguments' => array($admin_access),
109 'type' => MENU_NORMAL_ITEM,
110 );
111 $items['admin/project/cvs-accounts/list'] = array(
112 'title' => 'List',
113 'page callback' => 'cvs_account',
114 'access arguments' => array($admin_access),
115 'type' => MENU_DEFAULT_LOCAL_TASK,
116 );
117 $items['admin/project/cvs-accounts/import'] = array(
118 'title' => 'Import',
119 'page callback' => 'drupal_get_form',
120 'page arguments' => array('cvs_account_import'),
121 'access arguments' => array($admin_access),
122 'type' => MENU_LOCAL_TASK,
123 'weight' => 2,
124 );
125 $items['admin/project/cvs-accounts/export'] = array(
126 'title' => 'Export',
127 'page callback' => 'cvs_account_export',
128 'access arguments' => array($admin_access),
129 'type' => MENU_LOCAL_TASK,
130 'weight' => 3,
131 );
132 $items['admin/project/cvs-accounts/status'] = array(
133 'title' => 'Status',
134 'page callback' => 'cvs_account_status',
135 'access arguments' => array($admin_access),
136 'type' => MENU_LOCAL_TASK,
137 'weight' => 4,
138 );
139
140 // Repositories:
141 $items['admin/project/cvs-repositories'] = array(
142 'title' => 'CVS repositories',
143 'description' => 'Define and configure what CVS repositories are connected to your site, how CVS log messages should be saved, and how to integrate each repository with a source browsing tool such as ViewCVS.',
144 'page callback' => 'cvs_repository',
145 'access arguments' => array($admin_access),
146 'type' => MENU_NORMAL_ITEM,
147 );
148 $items['admin/project/cvs-repositories/list'] = array(
149 'title' => 'List',
150 'page callback' => 'cvs_repository',
151 'access arguments' => array($admin_access),
152 'type' => MENU_DEFAULT_LOCAL_TASK,
153 );
154 $items['admin/project/cvs-repositories/add'] = array(
155 'title' => 'Add repository',
156 'page callback' => 'drupal_get_form',
157 'page arguments' => array('cvs_repository_form'),
158 'access arguments' => array($admin_access),
159 'type' => MENU_LOCAL_TASK,
160 'weight' => 2,
161 );
162 $items['admin/project/cvs-repositories/edit'] = array(
163 'title' => 'Edit repository',
164 'page callback' => 'cvs_repository_edit',
165 'access arguments' => array($admin_access),
166 'type' => MENU_CALLBACK,
167 );
168 $items['admin/project/cvs-repositories/delete'] = array(
169 'title' => 'Delete',
170 'page callback' => 'cvs_repository_delete',
171 'access arguments' => array($admin_access),
172 'type' => MENU_CALLBACK,
173 );
174 $items['admin/project/cvs-repositories/fetch'] = array(
175 'title' => 'Fetch log',
176 'page callback' => 'cvs_repository_fetch',
177 'access arguments' => array($admin_access),
178 'type' => MENU_CALLBACK,
179 );
180
181
182 // If the user has a CVS account, add a 'track CVS messages' tab to the tracker page.
183 $items['user/%user/track/code'] = array(
184 'title' => 'Track code',
185 'page callback' => 'cvs_account_tracker',
186 'access callback' => 'cvs_account_tracker_access',
187 'access arguments' => array(1),
188 'type' => MENU_LOCAL_TASK,
189 'weight' => 2,
190 );
191
192 $items['node/%node/cvs-access'] = array(
193 'title' => 'CVS access',
194 'page callback' => 'drupal_get_form',
195 'page arguments' => array('cvs_project_access_form'),
196 'access callback' => 'cvs_project_access_form_access',
197 'access arguments' => array(1),
198 'type' => MENU_LOCAL_TASK,
199 'weight' => 4,
200 );
201 $items['node/%node/cvs-access/delete/%user'] = array(
202 'page callback' => 'drupal_get_form',
203 'page arguments' => array('cvs_project_delete_access', 1, 4),
204 'access callback' => 'cvs_project_access_form_access',
205 'access arguments' => array(1),
206 'type' => MENU_CALLBACK,
207 );
208 $items['node/%project_node/committers'] = array(
209 'title' => 'Committers',
210 'page callback' => 'cvs_project_committers_page',
211 'page arguments' => array(1),
212 'type' => MENU_CALLBACK,
213 'access callback' => 'node_access',
214 'access arguments' => array('view', 1),
215 );
216 // Redirect from the legacy URL to prevent link rot.
217 $items['project/developers/%project_node'] = array(
218 'title' => 'Developers',
219 'page callback' => 'cvs_project_developers_redirect',
220 'page arguments' => array(2),
221 'type' => MENU_CALLBACK,
222 'access callback' => 'node_access',
223 'access arguments' => array('view', 2),
224 );
225
226 return $items;
227 }
228
229 function cvs_account_tracker_access($account) {
230 $result = db_query('SELECT uid, status FROM {cvs_accounts} WHERE uid = %d', $account->uid);
231 if ($cvs_account = db_fetch_object($result)) {
232 return user_access('access CVS messages') && $cvs_account->status == CVS_APPROVED;
233 }
234
235 return FALSE;
236 }
237
238 function cvs_user_edit_form_access($account) {
239 global $user;
240 $result = db_query('SELECT uid, status FROM {cvs_accounts} WHERE uid = %d', $account->uid);
241 if ($cvs_account = db_fetch_object($result)) {
242 return (!empty($user->uid) && $user->uid == $account->uid && $cvs_account->status == CVS_APPROVED) || user_access('administer CVS');
243 }
244
245 return FALSE;
246 }
247
248 function cvs_project_access_form_access($node) {
249 global $user;
250 $result = db_query('SELECT uid FROM {cvs_accounts} WHERE uid = %d AND status = %d', $user->uid, CVS_APPROVED);
251 if ($cvs_account = db_fetch_object($result)) {
252 return $node->type == "project_project" && project_use_cvs($node) &&
253 ($node->uid == $user->uid || user_access('administer nodes')
254 || user_access('administer CVS'));
255 }
256
257 return FALSE;
258 }
259
260 /**
261 * Implementation of hook_user().
262 *
263 * Adds listing of CVS commits by project to the user profile page.
264 *
265 * Also, we need to hack this category in for the CVS edit subtab, since user
266 * module does try to have a tight control of management of these menu items,
267 * so we need to get it created through this hook and then altered by
268 * hook_menu_alter(). Drupal 7 should do better.
269 */
270 function cvs_user($op, &$edit, &$account, $category = NULL) {
271 switch ($op) {
272 case 'view':
273 // Print a list of projects the specified user contributed to.
274 if (!empty($account->uid)) {
275 $result = db_query("SELECT DISTINCT(f.nid), n.title, COUNT(f.nid) AS commits FROM {cvs_files} f INNER JOIN {node} n ON n.status = %d AND n.nid = f.nid WHERE f.uid = %d GROUP BY f.nid, n.title ORDER BY commits DESC", 1, $account->uid);
276 $total = 0;
277 $projects = array();
278 while ($project = db_fetch_object($result)) {
279 $project_link = l($project->title, 'node/'. $project->nid);
280 $projects[] = format_plural($project->commits, '!project (1 commit)', '!project (@count commits)', array('!project' => $project_link));
281 $total += $project->commits;
282 }
283 if ($total > 0) {
284 $projects[] = format_plural($total, 'Total: 1 commit', 'Total: @count commits');
285 }
286 if (!empty($projects)) {
287 $account->content['cvs'] = array(
288 '#type' => 'user_profile_category',
289 '#title' => t('Projects'),
290 '#weight' => 10,
291 '#attributes' => array('class' => 'cvs-project-user-commits'),
292 );
293 $account->content['cvs']['items'] = array(
294 '#type' => 'user_profile_item',
295 '#value' => theme('item_list', $projects),
296 );
297 }
298 }
299 break;
300
301 case 'categories':
302 return array(array('name' => 'cvs', 'title' => t('CVS'), 'weight' => 3));
303
304 }
305 }
306
307 /**
308 * Implementation of hook_menu_alter().
309 *
310 * See why we need this hack in cvs_user().
311 */
312 function cvs_menu_alter(&$callbacks) {
313 $callbacks['user/%user_category/edit/cvs']['page callback'] = 'drupal_get_form';
314 $callbacks['user/%user_category/edit/cvs']['page arguments'] = array('cvs_user_edit_form', 1);
315 $callbacks['user/%user_category/edit/cvs']['access callback'] = 'cvs_user_edit_form_access';
316 $callbacks['user/%user_category/edit/cvs']['access arguments'] = array(1);
317 }
318
319 /**
320 * Implementation of hook_perm().
321 */
322 function cvs_perm() {
323 return array('access CVS messages', 'administer CVS');
324 }
325
326 /**
327 * Implementation of hook_cron().
328 */
329 function cvs_cron() {
330 $result = db_query("SELECT * FROM {cvs_repositories} WHERE method = %d", 0);
331 while ($repo = db_fetch_object($result)) {
332 cvs_fetch_repository($repo);
333 }
334 }
335
336 /**
337 * Implementation of hook_theme().
338 */
339 function cvs_theme() {
340 return array(
341 'cvs_commit_message' => array(
342 'arguments' => array('commit'),
343 ),
344 'cvs_project_access_form' => array(
345 'arguments' => array('form'),
346 ),
347 'cvs_project_maintainer_list' => array(
348 'arguments' => array('node', 'maintainers'),
349 ),
350 );
351 }
352
353 /**
354 * Form builder function for system settings.
355 */
356 function cvs_settings_form(&$form_state) {
357 $strings = _cvs_get_strings();
358
359 $form['cvs_email_address'] = array(
360 '#type' => 'textfield',
361 '#title' => t('E-mail address'),
362 '#default_value' => variable_get('cvs_email_address', 'cvs@example.com'),
363 '#description' => t('The e-mail address of the CVS administrator.'),
364 );
365 $form['cvs_use_file'] = array(
366 '#type' => 'radios',
367 '#title' => t("Use file to store log"),
368 '#default_value' => variable_get('cvs_use_file', 1),
369 '#options' => array(t('Disabled'), t('Enabled')),
370 '#description' => t('When enabled, this will store log messages in a temporary file to reduce memory consumption.'),
371 );
372 $options = _cvs_get_repository_options('&lt;' . t('none') . '&gt;');
373 $form['cvs_default_repo'] = array(
374 '#title' => t('Default CVS repository for projects'),
375 '#type' => 'radios',
376 '#options' => $options,
377 '#default_value' => variable_get('cvs_default_repo', 0),
378 '#description' => t('Choose the repository that should be used by default when projects are being edited'),
379 );
380 $form['cvs_restrict_project_creation'] = array(
381 '#title' => t('Restrict project creation to users with CVS accounts'),
382 '#type' => 'checkbox',
383 '#default_value' => variable_get('cvs_restrict_project_creation', 1),
384 '#description' => t('If this box is checked, only users with CVS accounts will be allowed to create project nodes.'),
385 );
386 if (module_exists('project') && project_use_taxonomy()) {
387 $form['type_validation'] = array(
388 '#title' => t('Validate %cvs_dir by %type', array('%cvs_dir' => t('CVS directory'), '%type' => t('Project type'))),
389 '#type' => 'fieldset',
390 );
391 $form['type_validation']['cvs_directory_validate_by_type'] = array(
392 '#title' => t('Validate %cvs_dir using %type', array('%cvs_dir' => t('CVS directory'), '%type' => t('Project type'))),
393 '#type' => 'checkbox',
394 '#default_value' => variable_get('cvs_directory_validate_by_type', 1),
395 '#description' => t("If this box is checked, the first element of the path specified in the %cvs_dir field must match the selected %type for project nodes. This is a case insensitive comparison, and spaces in the %type field will be converted to hypens ('-') for the purpose of the comparison. However, if custom directories are specified in the fields below, those will be used for validation instead.", array('%cvs_dir' => t('CVS directory'), '%type' => t('Project type'))),
396 );
397 $terms = taxonomy_get_tree(_project_get_vid());
398 foreach ($terms as $i => $term) {
399 // Only use the first-level terms.
400 if ($term->depth == 0) {
401 $form['type_validation']['cvs_directory_tid_'. $term->tid] = array(
402 '#title' => t('Directory for %term_name', array('%term_name' => $term->name)),
403 '#type' => 'textfield',
404 '#default_value' => variable_get('cvs_directory_tid_'. $term->tid, ''),
405 );
406 }
407 }
408 }
409 $form['cvs_validate_by_short_name'] = array(
410 '#title' => t('Validate %cvs_dir using %short_name', array('%cvs_dir' => t('CVS directory'), '%short_name' => t('Project short name'))),
411 '#type' => 'checkbox',
412 '#default_value' => variable_get('cvs_validate_by_short_name', 1),
413 '#description' => t("If this box is checked, the last element of the path specified in the %cvs_dir field must match the %short_name field for project nodes.", array('%cvs_dir' => t('CVS directory'), '%short_name' => t('Project short name'))),
414 );
415 $form['cvs_directory_validate_dir_root_by_case'] = array(
416 '#title' => t('Ensure lowercase for first element of %cvs_dir', array('%cvs_dir' => t('CVS directory'))),
417 '#type' => 'checkbox',
418 '#default_value' => variable_get('cvs_directory_validate_dir_root_by_case', 1),
419 '#description' => t("If this box is checked, the first element of the path specified in the %cvs_dir field must be lowercase.", array('%cvs_dir' => t('CVS directory'))),
420 );
421 $form['cvs_list_per_page'] = array(
422 '#title' => t('Number of CVS accounts listed per page'),
423 '#type' => 'textfield',
424 '#default_value' => variable_get('cvs_list_per_page', 20),
425 '#size' => 6,
426 '#description' => t('The number of users to display per page in admin list view.')
427 );
428 $form['cvs_application_form'] = array(
429 '#title' => t('CVS application form strings'),
430 '#type' => 'fieldset',
431 '#collapsible' => TRUE,
432 '#collapsed' => TRUE
433 );
434 $form['cvs_application_form']['cvs_message_anon'] = array(
435 '#title' => t('Message to anonymous users'),
436 '#type' => 'textarea',
437 '#default_value' => variable_get('cvs_message_anon', $strings['cvs_message_anon']),
438 '#description' => t('Message to show to anonymous users who are trying to apply for a CVS account'),
439 );
440 $form['cvs_application_form']['cvs_message_auth'] = array(
441 '#title' => t('Message to authenticated users'),
442 '#type' => 'textarea',
443 '#default_value' => variable_get('cvs_message_auth', $strings['cvs_message_auth']),
444 '#description' => t('Message to show to authenticated users who are applying for a CVS account.'),
445 );
446 $form['cvs_application_form']['cvs_motivation_description'] = array(
447 '#title' => t('Motivation message description'),
448 '#type' => 'textarea',
449 '#default_value' => variable_get('cvs_motivation_description', $strings['cvs_motivation_description']),
450 '#description' => t('Text to display below the motivation field of a CVS application.'),
451 );
452 $form['cvs_email_form'] = array(
453 '#title' => t('CVS application e-mail messages'),
454 '#description' => t('The following messages are templates for e-mails messages
455 sent to users who have applied for a CVS account. The following variables are
456 substituted (where available) when the message is sent.
457 <ul>
458 <li>%account-name - The username of the applicant.</li>
459 <li>%user-account-url - The URL of the applicant\'s user page.</li>
460 <li>%manage-cvs-account-url : The URL where the user can manage his CVS account.</li>
461 <li>%cvs-admin-message : Additional guidelines specified by the CVS administrator.</li>
462 <li>%cvs-admin-name : Username of the CVS administrator.</li>
463 <li>%motivation-message : The motivation message of the applicant.</li>
464 <li>%client-information - The applicant\'s IP and browser related information.</li>
465 </ul>'),
466 '#type' => 'fieldset',
467 '#collapsible' => TRUE,
468 '#collapsed' => TRUE
469 );
470 $form['cvs_email_form']['cvs_received_email'] = array(
471 '#title' => t('CVS application received e-mail message'),
472 '#type' => 'textarea',
473 '#default_value' => variable_get('cvs_received_email', $strings['cvs_received_email']),
474 '#description' => t('The message to send to users whose application has been received.'),
475 );
476 $form['cvs_email_form']['cvs_new_application_email'] = array(
477 '#title' => t('CVS new application e-mail message'),
478 '#type' => 'textarea',
479 '#default_value' => variable_get('cvs_new_application_email', $strings['cvs_new_application_email']),
480 '#description' => t('The message sent to the administrator when a user has submitted an application.'),
481 );
482 $form['cvs_email_form']['cvs_approved_email'] = array(
483 '#title' => t('CVS account approved e-mail message'),
484 '#type' => 'textarea',
485 '#default_value' => variable_get('cvs_approved_email', $strings['cvs_approved_email']),
486 '#description' => t('The message to send to users whose accounts have been approved.'),
487 );
488 $form['cvs_email_form']['cvs_pending_email'] = array(
489 '#title' => t('CVS account pending e-mail message'),
490 '#type' => 'textarea',
491 '#default_value' => variable_get('cvs_pending_email', $strings['cvs_pending_email']),
492 '#description' => t('The message to send to users whose accounts cannot be approved yet.'),
493 );
494 $form['cvs_email_form']['cvs_declined_email'] = array(
495 '#title' => t('CVS account declined e-mail message'),
496 '#type' => 'textarea',
497 '#default_value' => variable_get('cvs_declined_email', $strings['cvs_declined_email']),
498 '#description' => t('The message to send to users whose accounts have been declined.'),
499 );
500 $form['cvs_email_form']['cvs_disabled_email'] = array(
501 '#title' => t('CVS account disabled e-mail message'),
502 '#type' => 'textarea',
503 '#default_value' => variable_get('cvs_disabled_email', $strings['cvs_disabled_email']),
504 '#description' => t('The message to send to users whose accounts have been disabled.'),
505 );
506
507 if (module_exists('project_release')) {
508 $form['cvs_message_new_release_branch'] = array(
509 '#title' => t('Message when new releases are added from a branch'),
510 '#type' => 'textarea',
511 '#default_value' => variable_get('cvs_message_new_release_branch', ''),
512 '#description' => t('Message to show to project maintainers when they add a new development snapshot release from a branch.'),
513 );
514 $form['cvs_message_new_release_tag'] = array(
515 '#title' => t('Message when new releases are added from a tag'),
516 '#type' => 'textarea',
517 '#default_value' => variable_get('cvs_message_new_release_tag', ''),
518 '#description' => t('Message to show to project maintainers when they add a new official release from a tag.'),
519 );
520 }
521 return system_settings_form($form);
522 }
523
524 /**
525 * Implementation of hook_nodeapi().
526 */
527 function cvs_nodeapi(&$node, $op, $arg = NULL) {
528 if ($node->type == 'project_project') {
529 switch ($op) {
530 case 'load':
531 $result = db_query('SELECT * FROM {cvs_projects} WHERE nid = %d', $node->nid);
532 if ($project = db_fetch_object($result)) {
533 $node->cvs['repository'] = $project->rid;
534 $node->cvs['directory'] = $project->directory;
535 }
536 break;
537 case 'insert':
538 case 'update':
539 _cvs_directory_fix($node->cvs['directory']);
540 db_query('DELETE FROM {cvs_projects} WHERE nid = %d', $node->nid);
541 db_query("INSERT INTO {cvs_projects} (nid, rid, directory) VALUES (%d, %d, '%s')", $node->nid, $node->cvs['repository'], $node->cvs['directory']);
542 db_query("UPDATE {cvs_files} SET nid = %d WHERE rid = %d AND file LIKE '%s%%'", $node->nid, $node->cvs['repository'], $node->cvs['directory']);
543 break;
544 case 'validate':
545 if (strlen($node->title) > 0) {
546 $admin = user_access('administer projects');
547 $project = !empty($node->nid) ? node_load($node->nid) : NULL;
548 if (!empty($node->cvs['repository']) && !db_result(db_query('SELECT rid FROM {cvs_repositories} WHERE rid = %d', $node->cvs['repository']))) {
549 form_set_error('cvs][repository', t('You must select a valid CVS repository.'));
550 }
551 if (!$admin && $project && $node->cvs['repository'] != $project->cvs['repository']) {
552 form_set_error('cvs][repository', t('You do not have permission to modify the CVS repository for this project.'));
553 }
554 if (empty($node->cvs['repository'])) {
555 // The rest of this validation only makes sense if we have
556 // a CVS repository for this project
557 if (!empty($node->cvs['directory'])) {
558 form_set_error('cvs][directory', t('You can not specify a CVS directory if there is no CVS repository for this project.'));
559 }
560 break;
561 }
562 _cvs_directory_fix($node->cvs['directory']);
563 if (!$node->cvs['directory']) {
564 form_set_error('cvs][directory', t('You have to specify a valid CVS directory.'));
565 }
566 else if (!preg_match('/^[a-zA-Z0-9\/_-]+$/', $node->cvs['directory'])) {
567 form_set_error('cvs][directory', t("The path of the CVS directory can only contain letters, numbers, slashes ('/'), hyphens ('-') and underscores ('_')."));
568 }
569 else if (preg_match('/\/\//', $node->cvs['directory'])) {
570 form_set_error('cvs][directory', t("The path of the CVS directory cannot contain two slashes next to each other '//'."));
571 }
572 else if (db_result(db_query("SELECT nid FROM {cvs_projects} WHERE nid != %d AND rid = %d AND directory = '%s'", $node->nid, $node->cvs['repository'], $node->cvs['directory']))) {
573 form_set_error('cvs][directory', t('The specified CVS directory conflicts with that of an existing project.'));
574 }
575 else if (!$admin) {
576 $path_elems = explode('/', $node->cvs['directory']);
577 // Remove empty elements caused by the trailing and leading '/'.
578 $path_elems = array_filter($path_elems);
579 if (variable_get('cvs_directory_validate_dir_root_by_case', 1)) {
580 if ($path_elems[1] != drupal_strtolower($path_elems[1])) {
581 form_set_error('cvs][directory', t("The root of the CVS directory (%root) must be lowercase.", array('%root' => $path_elems[1])));
582 }
583 }
584 if (project_use_taxonomy() && variable_get('cvs_directory_validate_by_type', 1)) {
585 $tree = taxonomy_get_term($node->project_type);
586 $cvs_dir_root = _cvs_dir_root($tree->name, $node->project_type);
587 if (drupal_strtolower($path_elems[1]) != $cvs_dir_root) {
588 form_set_error('cvs][directory', t("The root of the CVS directory (%root) does not match the selected project type (%type). By default, spaces in the type name are converted to hyphens in the path, but custom mappings can also be defined. Given the current project type, the first element of the directory path should be: %goal.", array('%root' => $path_elems[1], '%type' => $tree->name, '%goal' => $cvs_dir_root)));
589 }
590 }
591 if (variable_get('cvs_validate_by_short_name', 1)) {
592 $last_elem = array_pop($path_elems);
593 if ($last_elem != $node->project['uri']) {
594 form_set_error('cvs][directory', t("The last part of the CVS directory (%last) does not match the short project name (%short).", array('%last' => $last_elem, '%short' => $node->project['uri'])));
595 form_set_error('project][uri', t("The short project name (%short) does not match the last part of the CVS directory (%last).", array('%last' => $last_elem, '%short' => $node->project['uri'])));
596 }
597 }
598 }
599 }
600 break;
601 case 'delete':
602 db_query('DELETE FROM {cvs_projects} WHERE nid = %d', $node->nid);
603 break;
604 }
605 }
606 elseif ($node->type == 'project_release') {
607 switch ($op) {
608 case 'insert':
609 if (project_use_cvs($node->project)) {
610 if ($node->project_release['rebuild']) {
611 $msg = variable_get('cvs_message_new_release_branch', '');
612 }
613 else {
614 $msg = variable_get('cvs_message_new_release_tag', '');
615 }
616 if (!empty($msg)) {
617 drupal_set_message($msg);
618 }
619 }
620 break;
621 }
622 }
623 }
624
625 /**
626 * Implementation of hook_form_alter().
627 */
628 function cvs_form_alter(&$form, &$form_state, $form_id) {
629 if ($form_id == 'project_project_node_form') {
630 cvs_alter_project_project_form($form, $form_state);
631 return;
632 }
633 if ($form_id == 'project_release_node_form') {
634 if (function_exists('cvs_local_alter_project_release_form')) {
635 cvs_local_alter_project_release_form($form, $form_state);
636 }
637 cvs_alter_project_release_form($form, $form_state);
638 return;
639 }
640 }
641
642 /**
643 * Menu Callback: Display the CVS account edit form.
644 * Form builder function for cvs_user_edit_form()
645 */
646 function cvs_user_edit_form(&$form_state, $account) {
647 global $user;
648
649 $result = db_fetch_object(db_query("SELECT cvs_user, motivation, status FROM {cvs_accounts} WHERE uid = %d", $account->uid));
650 $cvs_user = $result->cvs_user;
651 $cvs_status = $result->status;
652 $cvs_motivation = $result->motivation;
653
654 $form['cvs'] = array(
655 '#type' => 'fieldset',
656 '#title' => t('CVS account settings'),
657 '#collapsible' => TRUE,
658 '#collapsed' => FALSE,
659 );
660 if (user_access('administer CVS')) {
661 // Due to the workaround being used in hook_menu to allow CVS administrators
662 // access to the user's account/edit section, the page title is incorrect.
663 $account = user_load(array('uid' => $account->uid));
664 drupal_set_title(check_plain($account->name));
665
666 $form['cvs']['cvs_user'] = array(
667 '#type' => 'textfield',
668 '#title' => t('CVS username'),
669 '#default_value' => $cvs_user,
670 '#size' => 30,
671 '#maxlength' => 64,
672 '#description' => t('The CVS username associated with this account. This field is used to link CVS messages to user accounts.'),
673 );
674 $form['cvs']['cvs_pass'] = array(
675 '#type' => 'password_confirm',
676 '#title' => t('CVS password'),
677 '#description' => t('To change the current CVS password, enter the new password in both fields.'),
678 );
679 $form['cvs']['motivation'] = array(
680 '#type' => 'item',
681 '#title' => t('Motivation message'),
682 '#value' => check_markup($cvs_motivation),
683 );
684 $form['cvs']['cvs_status'] = array(
685 '#type' => 'radios',
686 '#title' => t('CVS status'),
687 '#default_value' => $cvs_status,
688 '#options' => _cvs_get_status_options(),
689 '#description' => t("You can change the status of the user's CVS account."),
690 );
691 $form['cvs']['send_mail'] = array(
692 '#type' => 'checkbox',
693 '#title' => t('Inform the user by e-mail.'),
694 '#default_value' => 1,
695 );
696 $form['cvs']['message'] = array(
697 '#type' => 'textarea',
698 '#title' => t('Reason/Message'),
699 '#cols' => 50,
700 '#rows' => 5,
701 '#description' => t('The message you want to send to the user. This can be the reason for declining the application or an additional message after approval (used in e-mail).'),
702 );
703 }
704 elseif (strlen($cvs_user) && $account->uid == $user->uid) {
705 if($cvs_status) {
706 $form['cvs']['cvs_user'] = array(
707 '#type' => 'item',
708 '#title' => t('CVS username'),
709 '#value' => $cvs_user,
710 '#description' => t('Your CVS username. This field can only be edited by administrators and is used to link your CVS messages to your user account.'),
711 );
712 $form['cvs']['cvs_pass'] = array(
713 '#type' => 'password_confirm',
714 '#title' => t('CVS password'),
715 '#description' => t('To change your current CVS password, enter the new password in both fields.'),
716 );
717 }
718 else {
719 $form['cvs']['cvs_user'] = array(
720 '#type' => 'item',
721 '#title' => t('CVS username'),
722 '#value' => $cvs_user,
723 '#description' => t('Your CVS username associated with this account. Your CVS application has not been approved yet, it has been declined, or your account got blocked.'),
724 );
725 }
726 }
727 else {
728 $form['cvs']['cvs_user'] = array(
729 '#type' => 'item',
730 '#title' => t('CVS username'),
731 '#value' => $cvs_user,
732 '#description' => t('The CVS username associated with this account.'),
733 );
734 }
735 $form['cvs']['cvs_uid'] = array('#type' => 'value', '#value' => $account->uid);
736 $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
737
738 return $form;
739 }
740
741 /**
742 * Validate CVS user edit form submission.
743 */
744 function cvs_user_edit_form_validate($form, &$form_state) {
745 if (isset($form_state['values']['cvs_user'])) {
746 // Check for duplicates:
747 $id = db_result(db_query("SELECT uid FROM {cvs_accounts} WHERE cvs_user = '%s' AND uid != %d", $form_state['values']['cvs_user'], $form_state['values']['cvs_uid']));
748 if ($id != 0) {
749 form_set_error('cvs_user', t('The specified CVS username is already in use by user #!id.', array('!id' => $id)));
750 }
751 }
752 if (!empty($form_state['values']['cvs_pass']) && strlen($form_state['values']['cvs_pass']) < CVS_MIN_PASS_LENGTH) {
753 form_set_error('cvs_pass', t('The CVS password you have chosen is too short (it must be at least !min characters long).', array('!min' => CVS_MIN_PASS_LENGTH)));
754 }
755 }
756
757 /**
758 * Process CVS user edit form submission.
759 */
760 function cvs_user_edit_form_submit($form, &$form_state) {
761 global $user;
762
763 $result = db_fetch_object(db_query("SELECT cvs_user, status FROM {cvs_accounts} WHERE uid = %d", $form_state['values']['cvs_uid']));
764 $cvs_user = $result->cvs_user;
765 $cvs_status = $result->status;
766
767 if (user_access('administer CVS') && isset($form_state['values']['cvs_status'])) {
768 if ($form_state['values']['cvs_status'] == CVS_QUEUED && $cvs_status == CVS_QUEUED) {
769 // The QUEUED status should only indicate the initial status of an
770 // application. Once it has been replied to, it should be set to PENDING.
771 // In case the administrator forgets to do this, do it automatically.
772 $form_state['values']['cvs_status'] = CVS_PENDING;
773 }
774 if (isset($form_state['values']['cvs_pass']) && strlen($form_state['values']['cvs_pass'])) {
775 db_query("UPDATE {cvs_accounts} SET cvs_user = '%s', status = %d, pass = '%s' WHERE uid = %d", $form_state['values']['cvs_user'], $form_state['values']['cvs_status'], crypt($form_state['values']['cvs_pass']), $form_state['values']['cvs_uid']);
776 }
777 else {
778 db_query("UPDATE {cvs_accounts} SET cvs_user = '%s', status = %d WHERE uid = %d", $form_state['values']['cvs_user'], $form_state['values']['cvs_status'], $form_state['values']['cvs_uid']);
779 }
780 db_query("UPDATE {cvs_messages} SET cvs_user = '%s' WHERE uid = %d", $form_state['values']['cvs_user'], $form_state['values']['cvs_uid']);
781 if ($form_state['values']['send_mail']) {
782 cvs_mail_user($form_state['values']['cvs_uid'], $form_state['values']['message']);
783 }
784 drupal_set_message(t('The CVS account %account has been updated successfully.', array('%account' => $form_state['values']['cvs_user'])));
785 $form_state['redirect'] = 'admin/project/cvs-accounts';
786 }
787 elseif (strlen($cvs_user) && $form_state['values']['cvs_uid'] == $user->uid && isset($form_state['values']['cvs_pass']) && strlen($form_state['values']['cvs_pass'])) {
788 if($cvs_status) {
789 db_query("UPDATE {cvs_accounts} SET pass = '%s' WHERE uid = %d", crypt($form_state['values']['cvs_pass']), $form_state['values']['cvs_uid']);
790 drupal_set_message(t('Your CVS password has been updated successfully.'));
791 }
792 }
793 }
794
795 /**
796 * Alters the project_project node form to add CVS integration
797 * @see cvs_form_alter
798 *
799 * PROJECT TODO: this whole function needs to be reviewed
800 * after the 6.x project* port.
801 */
802 function cvs_alter_project_project_form(&$form, &$form_state) {
803 $node = $form['#node'];
804 $repositories = _cvs_get_repository_options(t('<none>'));
805 $form['cvs'] = array(
806 '#type' => 'fieldset',
807 '#title' => t('CVS integration'),
808 '#collapsible' => TRUE,
809 '#tree' => TRUE,
810 '#weight' => 4,
811 );
812 $default_repo = isset($node->cvs['repository']) ? $node->cvs['repository'] : variable_get('cvs_default_repo', 0);
813 if (count($repositories) > 1) {
814 $form['cvs']['repository'] = array(
815 '#type' => 'select',
816 '#title' => t('Repository'),
817 '#default_value' => $default_repo,
818 '#options' => user_access('administer projects') ? $repositories : array($default_repo => $repositories[$default_repo]),
819 '#description' => t("Specify the project's CVS repository."),
820 );
821 }
822 $form['cvs']['directory'] = array(
823 '#type' => 'textfield',
824 '#title' => t('CVS directory'),
825 '#default_value' => isset($node->cvs['directory']) ? $node->cvs['directory'] : '',
826 '#size' => 40,
827 '#maxlength' => 255,
828 '#description' => t("Specify the project's directory within the selected CVS repository. Directory names should start with a leading slash and end with a trailing slash, and must be unique for each project. For example: <code>/modules/foo/</code>, <code>/themes/foo/</code> or <code>/translations/foo/</code>. If there is no CVS repository associated with the project, this setting should be left blank."),
829 );
830 if ($default_repo == 0 || !empty($node->cvs['directory'])) {
831 // If the default for the repository selector is <none> (either
832 // because the project has already been saved with 0 for the
833 // repository, or because that's the site-wide default), or if we
834 // already have the value for the CVS directory, we should hide
835 // these gory details by default.
836 $form['cvs']['#collapsed'] = TRUE;
837 }
838 }
839
840 /**
841 * Alters the project_release node to add build tag UI
842 * @see cvs_form_alter
843 *
844 * PROJECT TODO: this whole function needs to be reviewed
845 * after the 6.x project* port.
846 */
847 function cvs_alter_project_release_form(&$form, &$form_state) {
848 // If we're not adding it, call a separate method that just worries
849 // about how to alter the edit form, since the add form is so complex.
850 if (arg(1) != 'add') {
851 return cvs_alter_project_release_form_edit($form, $form_state);
852 }
853
854 $project = $form['project']['#value'];
855
856 // Check to see if this project has a repository set. If not, then don't
857 // alter the form so that the project can have releases just as if the
858 // cvs.module were not enabled at all.
859 if (!project_use_cvs($project)) {
860 return;
861 }
862
863 if (!isset($project->project_release['releases'])) {
864 // This project has no releases, nothing to alter
865 return;
866 }
867
868 $fields = array('version_major', 'version_minor', 'version_patch');
869
870 if (isset($form_state['cvs_tag'])) {
871 $tag_name = $form_state['cvs_tag'];
872 }
873 elseif (isset($form_state['values']['project_release']['tag'])) {
874 $tag_name = $form_state['values']['project_release']['tag'];
875 }
876
877 if (!empty($tag_name)) {
878 $vid = _project_release_get_api_vid();
879 if ($tag_name == 'HEAD') {
880 // Special case validation for the HEAD...
881 $tag = new stdClass();
882 $tag->tag = 'HEAD';
883 $tag->branch = 1;
884 $tag->nid = $project->nid;
885
886 foreach ($fields as $field) {
887 if (isset($form_state['storage']['project_release'][$field])) {
888 $form_val = $form_state['storage']['project_release'][$field];
889 if ($form_val !== '' && (is_numeric($form_val) || $form_val == 'x')) {
890 $version->$field = $form_val;
891 }
892 }
893 }
894 if (isset($form_state['storage']['project_release']['version_extra'])
895 && $form_state['storage']['project_release']['version_extra'] !== '') {
896 $version->version_extra = $form_state['storage']['project_release']['version_extra'];
897 }
898 if (isset($form['taxonomy'][$vid])) {
899 if (isset($form_state['storage']['project_release']['version_api_tid'])) {
900 $version_api_tid = $form_state['storage']['project_release']['version_api_tid'];
901 if (is_numeric($version_api_tid) && !empty($version_api_tid) && db_result(db_query('SELECT COUNT(*) FROM {term_data} WHERE tid = %d AND vid = %d', $version_api_tid, $vid))) {
902 $version->version_api_tid = $version_api_tid;
903 $indexes = form_get_options($form['taxonomy'][$vid], $version_api_tid);
904 $options = array();
905 if ($indexes !== FALSE) {
906 foreach ($indexes as $index) {
907 $options[] = $form['taxonomy'][$vid]['#options'][$index];
908 }
909 $form['taxonomy'][$vid]['#options'] = $options;
910 $form['taxonomy'][$vid]['#default_value'] = $version_api_tid;
911 }
912 }
913 }
914 }
915
916 if (function_exists('cvs_local_version_is_valid')) {
917 $vers_is_valid = cvs_local_version_is_valid($version, $tag, $project);
918 }
919 else {
920 $vers_is_valid = isset($version);
921 }
922 if ($vers_is_valid) {
923 $version_str = project_release_get_version($version, $project);
924 }
925 else {
926 unset($version);
927 _cvs_alter_project_release_form_unset_all($form, array('taxonomy', 'project_release'));
928 $elements = element_children($form['taxonomy']);
929 $found_it = FALSE;
930 foreach ($elements as $element) {
931 if ($element != $vid) {
932 unset($form['taxonomy'][$element]);
933 }
934 else {
935 $found_it = TRUE;
936 }
937 }
938 if (!$found_it) {
939 // If we're not leaving the API selection element, unset the
940 // whole thing so we don't leave an empty fieldset.
941 unset($form['taxonomy']);
942 }
943 }
944 }
945 else {
946 $query = db_query("SELECT * FROM {cvs_tags} WHERE nid = %d AND tag = '%s'", $project->nid, $tag_name);
947 $tag = db_fetch_object($query);
948 if (!$tag) {
949 form_set_error('tag', t('The tag you selected does not exist for this project.'));
950 }
951 $query = db_query("SELECT nid FROM {project_release_nodes} WHERE pid = %d AND tag = '%s'", $project->nid, $tag_name);
952 if (db_result($query)) {
953 form_set_error('tag', t('The tag or branch you have selected is already in use by another release'));
954 }
955 $version = cvs_get_version_from_tag($tag, $project);
956 $version_str = project_release_get_version($version, $project);
957 }
958 unset($form['tag']);
959 $tags[$tag_name] = $tag_name;
960 $form['cvs_tag'] = array('#weight' => -4);
961 $form['cvs_tag']['project_release'] = array('#tree' => TRUE);
962 $form['cvs_tag']['project_release']['tag'] = array(
963 '#type' => 'select',
964 '#title' => $tag->branch ? t('CVS branch') : t('CVS tag'),
965 '#options' => $tags,
966 '#default_value' => $tag->tag,
967 '#required' => TRUE,
968 );
969 $form['project_release']['rebuild'] = array(
970 '#type' => 'value',
971 '#value' => $tag->branch,
972 );
973
974 if (isset($version_str)) {
975 // Since this is the final page, turn this into a fieldset with
976 // all the nice float/clear goodness
977 $form['cvs_tag']['#type'] = 'fieldset';
978 $form['cvs_tag']['#collapsible'] = TRUE;
979 $form['cvs_tag']['#title'] = t('Release identification');
980 $form['cvs_tag']['#prefix'] = '<div class="version-elements">';
981 $form['cvs_tag']['#suffix'] = '</div>';
982 $form['cvs_tag']['#description'] = t('Now that the CVS tag has been selected, these can not be modified.');
983
984 // Display the version string so the user knows we've got it right.
985 $form['cvs_tag']['version'] = array(
986 '#type' => 'textfield',
987 '#title' => 'Version string',
988 '#default_value' => $version_str,
989 '#attributes' => array('readonly' => TRUE, 'style' => 'width:auto'),
990 '#required' => TRUE,
991 '#size' => 30,
992 '#maxlength' => 40,
993 );
994 // Stash this in a form value so it'll make it through to validation
995 // where the title of the release node is set.
996 $form['cvs_tag']['project_release']['version'] = array(
997 '#type' => 'value',
998 '#value' => $version_str,
999 );
1000 // Also stash it in form_state['storage'] so we preserve it through
1001 // multi-page, preview, and submit.
1002 $form_state['storage']['project_release']['version'] = $version_str;
1003 }
1004 if (isset($version)) {
1005 foreach (array_merge($fields, array('version_extra')) as $field) {
1006 if (isset($version->$field)) {
1007 $form_state['storage']['project_release'][$field] = $version->$field;
1008 }
1009 $form['project_release'][$field]['#access'] = FALSE;
1010 }
1011 // If we already know the version info, which includes the
1012 // compatibility taxonomy, so rip out all other taxonomy options.
1013 if (isset($form['taxonomy'][$vid])) {
1014 $tid = $version->version_api_tid;
1015 $indexes = form_get_options($form['taxonomy'][$vid], $tid);
1016 if ($indexes !== FALSE) {
1017 $options = array();
1018 foreach ($indexes as $index) {
1019 $options[] = $form['taxonomy'][$vid]['#options'][$index];
1020 }
1021 $form['taxonomy'][$vid]['#options'] = $options;
1022 $form['taxonomy'][$vid]['#default_value'] = $tid;
1023 }
1024 }
1025 $form['project_release']['#access'] = FALSE;
1026 $form['rel_id']['#access'] = FALSE;
1027 // $form['#pre_render'][] = 'cvs_project_release_form_pre_render';
1028 // Add a submit handler to copy the version values from
1029