diff --git a/src/IDF/Form/Upload.php b/src/IDF/Form/Upload.php index 02c7019..8cde96c 100644 --- a/src/IDF/Form/Upload.php +++ b/src/IDF/Form/Upload.php @@ -79,6 +79,7 @@ class IDF_Form_Upload extends Pluf_Form public function clean_file() { + // FIXME: we do the same in IDF_Form_WikiResourceCreate and a couple of other places as well $extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext')))); if (strlen($extra)) $extra .= '|'; if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|war|wmv|zip)$/i', $this->cleaned_data['file'])) { diff --git a/src/IDF/Form/WikiCreate.php b/src/IDF/Form/WikiPageCreate.php similarity index 99% rename from src/IDF/Form/WikiCreate.php rename to src/IDF/Form/WikiPageCreate.php index 9c06c75..01df4ce 100644 --- a/src/IDF/Form/WikiCreate.php +++ b/src/IDF/Form/WikiPageCreate.php @@ -27,7 +27,7 @@ * This create a new page and the corresponding revision. * */ -class IDF_Form_WikiCreate extends Pluf_Form +class IDF_Form_WikiPageCreate extends Pluf_Form { public $user = null; public $project = null; diff --git a/src/IDF/Form/WikiDelete.php b/src/IDF/Form/WikiPageDelete.php similarity index 97% rename from src/IDF/Form/WikiDelete.php rename to src/IDF/Form/WikiPageDelete.php index 1595ecb..d7b952a 100644 --- a/src/IDF/Form/WikiDelete.php +++ b/src/IDF/Form/WikiPageDelete.php @@ -27,7 +27,7 @@ * This is a hard delete of the page and the revisions. * */ -class IDF_Form_WikiDelete extends Pluf_Form +class IDF_Form_WikiPageDelete extends Pluf_Form { protected $page = null; diff --git a/src/IDF/Form/WikiUpdate.php b/src/IDF/Form/WikiPageUpdate.php similarity index 99% rename from src/IDF/Form/WikiUpdate.php rename to src/IDF/Form/WikiPageUpdate.php index dae75f0..d164793 100644 --- a/src/IDF/Form/WikiUpdate.php +++ b/src/IDF/Form/WikiPageUpdate.php @@ -27,7 +27,7 @@ * This add a corresponding revision. * */ -class IDF_Form_WikiUpdate extends Pluf_Form +class IDF_Form_WikiPageUpdate extends Pluf_Form { public $user = null; public $project = null; diff --git a/src/IDF/Form/WikiResourceCreate.php b/src/IDF/Form/WikiResourceCreate.php new file mode 100644 index 0000000..a1edfa1 --- /dev/null +++ b/src/IDF/Form/WikiResourceCreate.php @@ -0,0 +1,169 @@ +project = $extra['project']; + $this->user = $extra['user']; + $initname = (!empty($extra['name'])) ? $extra['name'] : __('ResourceName'); + + $this->fields['title'] = new Pluf_Form_Field_Varchar( + array('required' => true, + 'label' => __('Resource title'), + 'initial' => $initname, + 'widget_attrs' => array( + 'maxlength' => 200, + 'size' => 67, + ), + 'help_text' => __('The resource name must contains only letters, digits and the dash (-) character.'), + )); + $this->fields['summary'] = new Pluf_Form_Field_Varchar( + array('required' => true, + 'label' => __('Description'), + 'help_text' => __('This one line description is displayed in the list of resources.'), + 'initial' => '', + 'widget_attrs' => array( + 'maxlength' => 200, + 'size' => 67, + ), + )); + + $this->fields['file'] = new Pluf_Form_Field_File( + array('required' => true, + 'label' => __('File'), + 'initial' => '', + 'max_size' => Pluf::f('max_upload_size', 2097152), + 'move_function_params' => array('upload_path' => $this->getTempUploadPath(), + 'upload_path_create' => true, + 'upload_overwrite' => true), + )); + } + + public function clean_title() + { + $title = $this->cleaned_data['title']; + if (preg_match('/[^a-zA-Z0-9\-]/', $title)) { + throw new Pluf_Form_Invalid(__('The title contains invalid characters.')); + } + $sql = new Pluf_SQL('project=%s AND title=%s', + array($this->project->id, $title)); + $resources = Pluf::factory('IDF_Wiki_Resource')->getList(array('filter'=>$sql->gen())); + if ($resources->count() > 0) { + throw new Pluf_Form_Invalid(__('A resource with this title already exists.')); + } + return $title; + } + + public function clean_file() + { + // FIXME: we do the same in IDF_Form_Upload and a couple of other places as well + $extra = strtolower(implode('|', explode(' ', Pluf::f('idf_extra_upload_ext')))); + if (strlen($extra)) $extra .= '|'; + if (!preg_match('/\.('.$extra.'png|jpg|jpeg|gif|bmp|psd|tif|aiff|asf|avi|bz2|css|doc|eps|gz|jar|mdtext|mid|mov|mp3|mpg|ogg|pdf|ppt|ps|qt|ra|ram|rm|rtf|sdd|sdw|sit|sxi|sxw|swf|tgz|txt|wav|xls|xml|war|wmv|zip)$/i', $this->cleaned_data['file'])) { + @unlink($this->getTempUploadPath().$this->cleaned_data['file']); + throw new Pluf_Form_Invalid(__('For security reasons, you cannot upload a file with this extension.')); + } + return $this->cleaned_data['file']; + } + + /** + * If we have uploaded a file, but the form failed remove it. + * + */ + function failed() + { + if (!empty($this->cleaned_data['file']) + and file_exists($this->getTempUploadPath().$this->cleaned_data['file'])) { + @unlink($this->getTempUploadPath().$this->cleaned_data['file']); + } + } + + /** + * Save the model in the database. + * + * @param bool Commit in the database or not. If not, the object + * is returned but not saved in the database. + * @return Object Model with data set from the form. + */ + function save($commit=true) + { + if (!$this->isValid()) { + throw new Exception(__('Cannot save the model from an invalid form.')); + } + + $tempFile = $this->getTempUploadPath().$this->cleaned_data['file']; + list($mimeType, , $extension) = IDF_FileUtil::getMimeType($tempFile); + + // create the resource + $resource = new IDF_Wiki_Resource(); + $resource->project = $this->project; + $resource->submitter = $this->user; + $resource->summary = trim($this->cleaned_data['summary']); + $resource->title = trim($this->cleaned_data['title']); + $resource->mime_type = $mimeType; + $resource->orig_file_ext = $extension; + $resource->create(); + + // add the first revision + $rev = new IDF_Wiki_ResourceRevision(); + $rev->wikiresource = $resource; + $rev->submitter = $this->user; + $rev->summary = __('Initial resource creation'); + $rev->filesize = filesize($tempFile); + $rev->create(); + + $finalFile = $rev->getFilePath(); + if (!@mkdir(dirname($finalFile), 0755, true)) { + @unlink($tempFile); + $rev->delete(); + $resource->delete(); + throw new Exception('could not create final resource path'); + } + + if (!@rename($tempFile, $finalFile)) { + @unlink($tempFile); + $rev->delete(); + $resource->delete(); + throw new Exception('could not move resource to final location'); + } + + return $resource; + } + + private function getTempUploadPath() + { + return Pluf::f('upload_path').'/'.$this->project->shortname.'/wiki/temp/'; + } +} diff --git a/src/IDF/Views/Wiki.php b/src/IDF/Views/Wiki.php index 0e75c47..a70617b 100644 --- a/src/IDF/Views/Wiki.php +++ b/src/IDF/Views/Wiki.php @@ -77,6 +77,44 @@ class IDF_Views_Wiki $request); } + /** + * View list of resources for a given project. + */ + public $listResources_precond = array('IDF_Precondition::accessWiki', + 'Pluf_Precondition::loginRequired'); + public function listResources($request, $match) + { + $prj = $request->project; + $title = sprintf(__('%s Documentation Resources'), (string) $prj); + $pag = new Pluf_Paginator(new IDF_Wiki_Resource()); + $pag->class = 'recent-issues'; + $pag->item_extra_props = array('project_m' => $prj, + 'shortname' => $prj->shortname, + 'current_user' => $request->user); + $pag->summary = __('This table shows the resources that can be used on documentation pages.'); + $pag->action = array('IDF_Views_Wiki::listResources', array($prj->shortname)); + $pag->edit_action = array('IDF_Views_Wiki::viewResource', 'shortname', 'title'); + $pag->forced_where = new Pluf_SQL('project=%s', array($prj->id)); + $pag->extra_classes = array('right', 'a-c', 'a-c', 'a-c'); + $list_display = array( + 'title' => __('Resource Title'), + 'mime_type' => __('MIME type'), + 'summary' => __('Description'), + array('modif_dtime', 'Pluf_Paginator_DateYMD', __('Updated')), + ); + $pag->configure($list_display, array(), array('title', 'modif_dtime')); + $pag->items_per_page = 25; + $pag->no_results_text = __('No resources were found.'); + $pag->sort_order = array('title', 'ASC'); + $pag->setFromRequest($request); + return Pluf_Shortcuts_RenderToResponse('idf/wiki/listResources.html', + array( + 'page_title' => $title, + 'resources' => $pag, + ), + $request); + } + public $search_precond = array('IDF_Precondition::accessWiki',); public function search($request, $match) { @@ -173,7 +211,7 @@ class IDF_Views_Wiki $title = __('New Page'); $preview = false; if ($request->method == 'POST') { - $form = new IDF_Form_WikiCreate($request->POST, + $form = new IDF_Form_WikiPageCreate($request->POST, array('project' => $prj, 'user' => $request->user )); @@ -191,7 +229,7 @@ class IDF_Views_Wiki } else { $pagename = (isset($request->GET['name'])) ? $request->GET['name'] : ''; - $form = new IDF_Form_WikiCreate(null, + $form = new IDF_Form_WikiPageCreate(null, array('name' => $pagename, 'project' => $prj, 'user' => $request->user)); @@ -206,6 +244,43 @@ class IDF_Views_Wiki $request); } + /** + * Create a new resource. + */ + public $createResource_precond = array('IDF_Precondition::accessWiki', + 'Pluf_Precondition::loginRequired'); + public function createResource($request, $match) + { + $prj = $request->project; + $title = __('New Resource'); + $preview = false; + if ($request->method == 'POST') { + $form = new IDF_Form_WikiResourceCreate(array_merge($request->POST, $request->FILES), + array('project' => $prj, 'user' => $request->user)); + if ($form->isValid()) { + $resource = $form->save(); + $urlresource = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewResource', + array($prj->shortname, $resource->title)); + $request->user->setMessage(sprintf(__('The resource %s has been created.'), $urlresource, Pluf_esc($resource->title))); + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::listResources', + array($prj->shortname)); + return new Pluf_HTTP_Response_Redirect($url); + } + } else { + $resourcename = (isset($request->GET['name'])) ? + $request->GET['name'] : ''; + $form = new IDF_Form_WikiResourceCreate(null, + array('name' => $resourcename, + 'project' => $prj, 'user' => $request->user)); + } + return Pluf_Shortcuts_RenderToResponse('idf/wiki/createResource.html', + array( + 'resource_title' => $title, + 'form' => $form, + ), + $request); + } + /** * View a documentation page. */ @@ -314,7 +389,7 @@ class IDF_Views_Wiki 'user' => $request->user, 'page' => $page); if ($request->method == 'POST') { - $form = new IDF_Form_WikiUpdate($request->POST, $params); + $form = new IDF_Form_WikiPageUpdate($request->POST, $params); if ($form->isValid() and !isset($request->POST['preview'])) { $page = $form->save(); $urlpage = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::viewPage', @@ -328,7 +403,7 @@ class IDF_Views_Wiki } } else { - $form = new IDF_Form_WikiUpdate(null, $params); + $form = new IDF_Form_WikiPageUpdate(null, $params); } return Pluf_Shortcuts_RenderToResponse('idf/wiki/updatePage.html', array( @@ -354,7 +429,7 @@ class IDF_Views_Wiki $prj->inOr404($page); $params = array('page' => $page); if ($request->method == 'POST') { - $form = new IDF_Form_WikiDelete($request->POST, $params); + $form = new IDF_Form_WikiPageDelete($request->POST, $params); if ($form->isValid()) { $form->save(); $request->user->setMessage(__('The documentation page has been deleted.')); @@ -363,7 +438,7 @@ class IDF_Views_Wiki return new Pluf_HTTP_Response_Redirect($url); } } else { - $form = new IDF_Form_WikiDelete(null, $params); + $form = new IDF_Form_WikiPageDelete(null, $params); } $title = sprintf(__('Delete Page %s'), $page->title); $revision = $page->get_current_revision(); diff --git a/src/IDF/Wiki/Resource.php b/src/IDF/Wiki/Resource.php index 1504682..cc4c318 100644 --- a/src/IDF/Wiki/Resource.php +++ b/src/IDF/Wiki/Resource.php @@ -72,6 +72,14 @@ class IDF_Wiki_Resource extends Pluf_Model 'verbose' => __('MIME media type'), 'help_text' => __('The MIME media type of the resource.'), ), + 'orig_file_ext' => + array( + 'type' => 'Pluf_DB_Field_Varchar', + 'blank' => false, + 'size' => 10, + 'verbose' => __('Original file extension'), + 'help_text' => __('The original file extension of the uploaded resource.'), + ), 'summary' => array( 'type' => 'Pluf_DB_Field_Varchar', diff --git a/src/IDF/Wiki/ResourceRevision.php b/src/IDF/Wiki/ResourceRevision.php index e29b0c2..43eadd4 100644 --- a/src/IDF/Wiki/ResourceRevision.php +++ b/src/IDF/Wiki/ResourceRevision.php @@ -109,78 +109,37 @@ class IDF_Wiki_ResourceRevision extends Pluf_Model { if ($this->id == '') { $this->creation_dtime = gmdate('Y-m-d H:i:s'); + $this->is_head = true; } } function postSave($create=false) { if ($create) { - IDF_Timeline::insert($this, $this->get_project(), - $this->get_submitter(), $this->creation_dtime); + $sql = new Pluf_SQL('wikiresource=%s', array($this->wikiresource)); + $rev = Pluf::factory('IDF_Wiki_ResourceRevision')->getList(array('filter'=>$sql->gen())); + if ($rev->count() > 1) { + foreach ($rev as $r) { + if ($r->id != $this->id and $r->is_head) { + $r->is_head = false; + $r->update(); + } + } + } + // update the modification timestamp + $resource = $this->get_wikiresource(); + $resource->update(); } } - function getAbsoluteUrl($project) + function getFilePath() { - return Pluf::f('url_upload').'/'.$project->shortname.'/files/'.$this->file; + return sprintf(Pluf::f('upload_path').'/'.$this->get_wikiresource()->get_project()->shortname.'/wiki/res/%d/%d.%s', + $this->get_wikiresource()->id, $this->id, $this->get_wikiresource()->orig_file_ext); } - function getFullPath() - { - return(Pluf::f('upload_path').'/'.$this->get_project()->shortname.'/files/'.$this->file); - } - - /** - * We drop the information from the timeline. - */ function preDelete() { - IDF_Timeline::remove($this); - @unlink(Pluf::f('upload_path').'/'.$this->project->shortname.'/files/'.$this->file); - } - - /** - * Returns the timeline fragment for the file. - * - * - * @param Pluf_HTTP_Request - * @return Pluf_Template_SafeString - */ - public function timelineFragment($request) - { - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::view', - array($request->project->shortname, - $this->id)); - $out = ''. - Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')). - ''; - $stag = new IDF_Template_ShowUser(); - $user = $stag->start($this->get_submitter(), $request, '', false); - $out .= sprintf(__('Download %2$d, %3$s'), $url, $this->id, Pluf_esc($this->summary)).''; - $out .= ''; - $out .= "\n".' -
'.sprintf(__('Addition of download %d, by %s'), $url, $this->id, $user).'
'; - return Pluf_Template::markSafe($out); - } - - public function feedFragment($request) - { - $url = Pluf::f('url_base') - .Pluf_HTTP_URL_urlForView('IDF_Views_Download::view', - array($request->project->shortname, - $this->id)); - $title = sprintf(__('%s: Download %d added - %s'), - $request->project->name, - $this->id, $this->summary); - $date = Pluf_Date::gmDateToGmString($this->creation_dtime); - $context = new Pluf_Template_Context_Request( - $request, - array('url' => $url, - 'title' => $title, - 'file' => $this, - 'date' => $date) - ); - $tmpl = new Pluf_Template('idf/downloads/feedfragment.xml'); - return $tmpl->render($context); + @unlink($this->getFilePath()); } } diff --git a/src/IDF/conf/urls.php b/src/IDF/conf/urls.php index 36d60f7..71e30f6 100644 --- a/src/IDF/conf/urls.php +++ b/src/IDF/conf/urls.php @@ -267,11 +267,21 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/$#', 'model' => 'IDF_Views_Wiki', 'method' => 'listPages'); +$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/$#', + 'base' => $base, + 'model' => 'IDF_Views_Wiki', + 'method' => 'listResources'); + $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/create/$#', 'base' => $base, 'model' => 'IDF_Views_Wiki', 'method' => 'createPage'); +$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/create/$#', + 'base' => $base, + 'model' => 'IDF_Views_Wiki', + 'method' => 'createResource'); + $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/search/$#', 'base' => $base, 'model' => 'IDF_Views_Wiki', @@ -287,21 +297,41 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/update/(.*)/$#', 'model' => 'IDF_Views_Wiki', 'method' => 'updatePage'); +$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/update/(.*)/$#', + 'base' => $base, + 'model' => 'IDF_Views_Wiki', + 'method' => 'updateResource'); + $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/delrev/(\d+)/$#', 'base' => $base, 'model' => 'IDF_Views_Wiki', 'method' => 'deletePageRev'); +$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/delrev/(\d+)/$#', + 'base' => $base, + 'model' => 'IDF_Views_Wiki', + 'method' => 'deleteResourceRev'); + $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/delete/(\d+)/$#', 'base' => $base, 'model' => 'IDF_Views_Wiki', 'method' => 'deletePage'); +$ctl[] = array('regex' => '#^/p/([\-\w]+)/res/delete/(\d+)/$#', + 'base' => $base, + 'model' => 'IDF_Views_Wiki', + 'method' => 'deleteResource'); + $ctl[] = array('regex' => '#^/p/([\-\w]+)/page/(.*)/$#', 'base' => $base, 'model' => 'IDF_Views_Wiki', 'method' => 'viewPage'); +$ctl[] = array('regex' => '#^/p/([\-\w]+)/resouce/(.*)/$#', + 'base' => $base, + 'model' => 'IDF_Views_Wiki', + 'method' => 'viewResource'); + // ---------- Downloads ------------------------------------ $ctl[] = array('regex' => '#^/help/archive-format/$#', diff --git a/src/IDF/templates/idf/wiki/base.html b/src/IDF/templates/idf/wiki/base.html index 807c271..ff9ab19 100644 --- a/src/IDF/templates/idf/wiki/base.html +++ b/src/IDF/templates/idf/wiki/base.html @@ -3,10 +3,18 @@ {block subtabs}
{trans 'List Pages'} -{if !$user.isAnonymous()} | {trans 'New Page'} {/if} -{if !$user.isAnonymous() and $inPageView} | {trans 'Update This Page'} {/if} - | -
+{if !$user.isAnonymous()} + | {trans 'List Resources'} + | {trans 'New Page'} + | {trans 'New Resource'} + {if $inPageView} + | {trans 'Update This Page'} + {/if} + {if $inResourceView} + | {trans 'Update This Resource'} + {/if} +{/if} +|
diff --git a/src/IDF/templates/idf/wiki/createPage.html b/src/IDF/templates/idf/wiki/createPage.html index e54ee5f..b5cdf48 100644 --- a/src/IDF/templates/idf/wiki/createPage.html +++ b/src/IDF/templates/idf/wiki/createPage.html @@ -1,5 +1,5 @@ {extends "idf/wiki/base.html"} -{block docclass}yui-t1{assign $inCreate = true}{/block} +{block docclass}yui-t1{assign $inCreatePage = true}{/block} {block body} {if $preview} diff --git a/src/IDF/templates/idf/wiki/createResource.html b/src/IDF/templates/idf/wiki/createResource.html new file mode 100644 index 0000000..8c0e5ec --- /dev/null +++ b/src/IDF/templates/idf/wiki/createResource.html @@ -0,0 +1,56 @@ +{extends "idf/wiki/base.html"} +{block docclass}yui-t1{assign $inCreateResource = true}{/block} +{block body} + +{if $form.errors} +
+

{trans 'The form contains some errors. Please correct them to create the page.'}

+{if $form.get_top_errors} +{$form.render_top_errors|unsafe} +{/if} +
+{/if} + +
+ + + + + + + + + + + + + + + + +
{$form.f.title.labelTag}:{if $form.f.title.errors}{$form.f.title.fieldErrors}{/if} +{$form.f.title|unsafe}
+{$form.f.title.help_text} +
{$form.f.summary.labelTag}:{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if} +{$form.f.summary|unsafe}
+{$form.f.summary.help_text} +
{$form.f.file.labelTag}:{if $form.f.file.errors}{$form.f.file.fieldErrors}{/if} +{$form.f.file|unsafe} +
  | {trans 'Cancel'} +
+
+{/block} +{block context} +
+{blocktrans} +Wiki resources are later addressed in wiki pages by their title, so ensure that you +give your resource a unique and an easy to remember name. +{/blocktrans} +
+{/block} +{block javascript} + +{/block} + diff --git a/src/IDF/templates/idf/wiki/listResources.html b/src/IDF/templates/idf/wiki/listResources.html new file mode 100644 index 0000000..dd7d5f2 --- /dev/null +++ b/src/IDF/templates/idf/wiki/listResources.html @@ -0,0 +1,12 @@ +{extends "idf/wiki/base.html"} +{block docclass}yui-t1{assign $inResourceList=true}{/block} +{block body} +{$resources.render} +{aurl 'url', 'IDF_Views_Wiki::createResource', array($project.shortname)} +

+ {trans 'New Resource'}

+ +{/block} +{block context} +

{trans 'Number of resources:'} {$resources.nb_items}

+{/block} +