diff --git a/src/IDF/Form/Admin/ProjectDelete.php b/src/IDF/Form/Admin/ProjectDelete.php new file mode 100644 index 0000000..67ee0e5 --- /dev/null +++ b/src/IDF/Form/Admin/ProjectDelete.php @@ -0,0 +1,88 @@ +project = $extra['project']; + $this->user = $extra['user']; + $this->fields['code'] = new Pluf_Form_Field_Varchar( + array('required' => true, + 'label' => __('Confirmation code'), + 'initial' => '', + )); + $this->fields['agree'] = new Pluf_Form_Field_Boolean( + array('required' => true, + 'label' => __('I have made a backup of all the important data of this project.'), + 'initial' => '', + )); + } + + public function clean_code() + { + $code = $this->cleaned_data['code']; + if ($code != $this->getCode()) { + throw new Pluf_Form_Invalid(__('The confirmation code does not match. Please provide a valid confirmation code to delete the project.')); + } + return $code; + } + + public function clean_agree() + { + if (!$this->cleaned_data['agree']) { + throw new Pluf_Form_Invalid(__('Sorry, you really need to backup your data before deletion.')); + } + return $this->cleaned_data['agree']; + } + + public function getCode() + { + return substr(md5(Pluf::f('secret_key').$this->user->id.'.'.$this->project->id), + 0, 8); + } + + + public function save($commit=true) + { + if (!$this->isValid()) { + throw new Exception(__('Cannot save the model from an invalid form.')); + } + // So, we drop the project, it will cascade and delete all the + // elements of the project. For large projects, this may use + // quite some memory. + $this->project->delete(); + return true; + } +} + + diff --git a/src/IDF/Project.php b/src/IDF/Project.php index 31bae65..06960d4 100644 --- a/src/IDF/Project.php +++ b/src/IDF/Project.php @@ -426,6 +426,57 @@ class IDF_Project extends Pluf_Model } /** + * Get simple statistics about the project. + * + * This returns an associative array with number of tickets, + * number of downloads, etc. + * + * @return array Stats + */ + public function getStats() + { + $stats = array(); + $stats['total'] = 0; + $what = array('downloads' => 'IDF_Upload', + 'reviews' => 'IDF_Review', + 'issues' => 'IDF_Issue', + 'docpages' => 'IDF_WikiPage', + 'commits' => 'IDF_Commit', + ); + foreach ($what as $key=>$m) { + $i = Pluf::factory($m)->getCount(array('filter' => 'project='.(int)$this->id)); + $stats[$key] = $i; + $stats['total'] += $i; + } + /** + * [signal] + * + * IDF_Project::getStats + * + * [sender] + * + * IDF_Project + * + * [description] + * + * This signal allows an application to update the statistics + * array of a project. For example to add the on disk size + * of the repository if available. + * + * [parameters] + * + * array('project' => $project, + * 'stats' => $stats) + * + */ + $params = array('project' => $this, + 'stats' => $stats); + Pluf_Signal::send('IDF_Project::getStats', + 'IDF_Project', $params); + return $stats; + } + + /** * Needs to be called when you update the memberships of a * project. * diff --git a/src/IDF/Views/Admin.php b/src/IDF/Views/Admin.php index d9693c0..8eed08e 100644 --- a/src/IDF/Views/Admin.php +++ b/src/IDF/Views/Admin.php @@ -143,6 +143,39 @@ class IDF_Views_Admin } /** + * Deletion of a project. + * + * Only the forge administrator can perform this operation. + */ + public $projectDelete_precond = array('Pluf_Precondition::adminRequired'); + public function projectDelete($request, $match) + { + $project = Pluf_Shortcuts_GetObjectOr404('IDF_Project', $match[1]); + $title = sprintf(__('Delete %s Project'), $project); + $extra = array('project' => $project, + 'user' => $request->user); + if ($request->method == 'POST') { + $form = new IDF_Form_Admin_ProjectDelete($request->POST, $extra); + if ($form->isValid()) { + $project = $form->save(); + $request->user->setMessage(__('The project has been deleted.')); + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::projects'); + return new Pluf_HTTP_Response_Redirect($url); + } + } else { + $form = new IDF_Form_Admin_ProjectDelete(null, $extra); + } + return Pluf_Shortcuts_RenderToResponse('idf/gadmin/projects/delete.html', + array( + 'page_title' => $title, + 'form' => $form, + 'stats' => $project->getStats(), + 'code' => $form->getCode(), + ), + $request); + } + + /** * Users overview. * */ diff --git a/src/IDF/conf/urls.php b/src/IDF/conf/urls.php index 57805cb..b75c869 100644 --- a/src/IDF/conf/urls.php +++ b/src/IDF/conf/urls.php @@ -417,6 +417,12 @@ $ctl[] = array('regex' => '#^/admin/projects/create/$#', 'model' => 'IDF_Views_Admin', 'method' => 'projectCreate'); +$ctl[] = array('regex' => '#^/admin/projects/(\d+)/delete/$#', + 'base' => $base, + 'priority' => 4, + 'model' => 'IDF_Views_Admin', + 'method' => 'projectDelete'); + $ctl[] = array('regex' => '#^/admin/users/$#', 'base' => $base, 'priority' => 4, diff --git a/src/IDF/templates/idf/gadmin/projects/delete.html b/src/IDF/templates/idf/gadmin/projects/delete.html new file mode 100644 index 0000000..bbf14ae --- /dev/null +++ b/src/IDF/templates/idf/gadmin/projects/delete.html @@ -0,0 +1,72 @@ +{extends "idf/gadmin/projects/base.html"} +{block body} +{if $form.errors} +
+

{trans 'The form contains some errors. Please correct them to delete the project.'}

+{if $form.get_top_errors} +{$form.render_top_errors|unsafe} +{/if} +
+{/if} + +{trans 'Project Statistics'} + + + + + + + + + + + + + +
{trans 'Tab'}{trans 'Number'}
{trans 'Downloads'}{$stats['downloads']}
{trans 'Code reviews'}{$stats['reviews']}
{trans 'Commits'}{$stats['commits']}
{trans 'Issues'}{$stats['issues']}
{trans 'Documentation pages'}{$stats['docpages']}
+ +

{trans 'Delete Project'}

+ +
+ + + + + + + + + + + + + +
  +{blocktrans}Confirmation code to confirm the deletion of the project: +{$code}.{/blocktrans}
+
+{$form.f.code.labelTag}: +{if $form.f.code.errors}{$form.f.code.fieldErrors}{/if} +{$form.f.code|unsafe} +
  +{if $form.f.agree.errors}{$form.f.agree.fieldErrors}{/if} +{$form.f.agree|unsafe} {$form.f.agree.labelTag} +
  + +| {trans 'Cancel'}{if $stats['total'] > 200}
+{trans 'For large projects, the suppression can take a while, please be patient.'}{/if} +
+
+{/block} +{block context} +
+

{blocktrans} +Attention! Deleting a project is a one second operation +with the consequences that all the data related to the +project will be deleted. +{/blocktrans}

+
+{/block} + + + diff --git a/src/IDF/templates/idf/gadmin/projects/update.html b/src/IDF/templates/idf/gadmin/projects/update.html index 5cd9b24..bdda67c 100644 --- a/src/IDF/templates/idf/gadmin/projects/update.html +++ b/src/IDF/templates/idf/gadmin/projects/update.html @@ -34,9 +34,10 @@   - + {aurl 'url', 'IDF_Views_Admin::projectDelete', array($project.id)} -| {trans 'Cancel'} +| {trans 'Cancel'} {if $isAdmin} + {trans 'Trash'} {trans 'Delete this project'}
{trans 'You will be ask to confirm.'}
{/if} diff --git a/www/media/idf/css/style.css b/www/media/idf/css/style.css index dace477..e1cc85c 100644 --- a/www/media/idf/css/style.css +++ b/www/media/idf/css/style.css @@ -142,6 +142,10 @@ table.recent-issues { width: 90%; } +table.minsize { + width: auto !important; +} + table.recent-issues tr.log { border-bottom: 1px solid #e7ebe3; }