Partial fix of issue 55, addition of a simple Wiki.

Added a base wiki, it is now possible to create wiki pages and update
them. Revisions are kept also not used/displayed at the moment.
This commit is contained in:
Loic d'Anterroches 2008-11-22 23:51:23 +01:00
parent b13633fed2
commit 8eb5715656
32 changed files with 1746 additions and 91 deletions

View File

@ -36,6 +36,7 @@ class IDF_Form_TabsConf extends Pluf_Form
$this->project = $extra['project']; $this->project = $extra['project'];
$ak = array('downloads_access_rights' => __('Downloads'), $ak = array('downloads_access_rights' => __('Downloads'),
'wiki_access_rights' => __('Documentation'),
'source_access_rights' => __('Source'), 'source_access_rights' => __('Source'),
'issues_access_rights' => __('Issues'),); 'issues_access_rights' => __('Issues'),);
foreach ($ak as $key=>$label) { foreach ($ak as $key=>$label) {

66
src/IDF/Form/WikiConf.php Normal file
View File

@ -0,0 +1,66 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Configuration of the labels etc. for the wiki pages.
*/
class IDF_Form_WikiConf extends Pluf_Form
{
/**
* Defined as constants to easily access the value in the
* form in the case nothing is in the db yet.
*/
const init_predefined = 'Featured = Listed on project home page
Phase:Requirements = Project vision and requirements
Phase:Design = Project design and key concerns
Phase:Implementation = Developers\' guide
Phase:QA = Testing plans and QA strategies
Phase:Deploy = How to install and configure the program
Phase:Support = Plans for user support and advocacy
Deprecated = Most users should NOT reference this';
const init_one_max = '';
public function initFields($extra=array())
{
$this->fields['labels_wiki_predefined'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Predefined documentation page labels'),
'initial' => self::init_predefined,
'widget_attrs' => array('rows' => 13,
'cols' => 75),
'widget' => 'Pluf_Form_Widget_TextareaInput',
));
$this->fields['labels_wiki_one_max'] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Each documentation page may have at most one label with each of these classes'),
'initial' => self::init_one_max,
'widget_attrs' => array('size' => 60),
));
}
}

203
src/IDF/Form/WikiCreate.php Normal file
View File

@ -0,0 +1,203 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Create a new documentation page.
*
* This create a new page and the corresponding revision.
*
*/
class IDF_Form_WikiCreate extends Pluf_Form
{
public $user = null;
public $project = null;
public $show_full = false;
public function initFields($extra=array())
{
$initial = __('# Introduction
Add your content here.
# Details
Add your content here. Format your content with:
* Text in **bold** or *italic*.
* Headings, paragraphs, and lists.
* Links to other [[WikiPage]].
');
$this->user = $extra['user'];
$this->project = $extra['project'];
if ($this->user->hasPerm('IDF.project-owner', $this->project)
or $this->user->hasPerm('IDF.project-member', $this->project)) {
$this->show_full = true;
}
$this->fields['title'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Page title'),
'initial' => __('PageName'),
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
'help_text' => __('The page 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 pages.'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
$this->fields['content'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Content'),
'initial' => $initial,
'widget' => 'Pluf_Form_Widget_TextareaInput',
'widget_attrs' => array(
'cols' => 58,
'rows' => 26,
),
));
if ($this->show_full) {
for ($i=1;$i<4;$i++) {
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Labels'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 50,
'size' => 20,
),
));
}
}
}
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));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
if ($pages->count() > 0) {
throw new Pluf_Form_Invalid(__('A page with this title already exists.'));
}
return $title;
}
/**
* Validate the interconnection in the form.
*/
public function clean()
{
if (!$this->show_full) {
return $this->cleaned_data;
}
$conf = new IDF_Conf();
$conf->setProject($this->project);
$onemax = array();
foreach (split(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
if (trim($class) != '') {
$onemax[] = mb_strtolower(trim($class));
}
}
$count = array();
for ($i=1;$i<4;$i++) {
$this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]);
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(mb_strtolower(trim($class)),
trim($name));
} else {
$class = 'other';
$name = $this->cleaned_data['label'.$i];
}
if (!isset($count[$class])) $count[$class] = 1;
else $count[$class] += 1;
if (in_array($class, $onemax) and $count[$class] > 1) {
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to a page.'), $class);
throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
}
}
return $this->cleaned_data;
}
/**
* 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.'));
}
// Add a tag for each label
$tags = array();
if ($this->show_full) {
for ($i=1;$i<4;$i++) {
if (strlen($this->cleaned_data['label'.$i]) > 0) {
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(trim($class), trim($name));
} else {
$class = 'Other';
$name = trim($this->cleaned_data['label'.$i]);
}
$tags[] = IDF_Tag::add($name, $this->project, $class);
}
}
}
// Create the page
$page = new IDF_WikiPage();
$page->project = $this->project;
$page->submitter = $this->user;
$page->summary = trim($this->cleaned_data['summary']);
$page->title = trim($this->cleaned_data['title']);
$page->create();
foreach ($tags as $tag) {
$page->setAssoc($tag);
}
// add the first revision
$rev = new IDF_WikiRevision();
$rev->wikipage = $page;
$rev->content = $this->cleaned_data['content'];
$rev->submitter = $this->user;
$rev->summary = __('Initial page creation');
$rev->create();
return $page;
}
}

241
src/IDF/Form/WikiUpdate.php Normal file
View File

@ -0,0 +1,241 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Update a documentation page.
*
* This add a corresponding revision.
*
*/
class IDF_Form_WikiUpdate extends Pluf_Form
{
public $user = null;
public $project = null;
public $page = null;
public $show_full = false;
public function initFields($extra=array())
{
$this->page = $extra['page'];
$this->user = $extra['user'];
$this->project = $extra['project'];
if ($this->user->hasPerm('IDF.project-owner', $this->project)
or $this->user->hasPerm('IDF.project-member', $this->project)) {
$this->show_full = true;
}
if ($this->show_full) {
$this->fields['title'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Page title'),
'initial' => $this->page->title,
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
'help_text' => __('The page 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 pages.'),
'initial' => $this->page->summary,
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
}
$rev = $this->page->get_current_revision();
$this->fields['content'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Content'),
'initial' => $rev->content,
'widget' => 'Pluf_Form_Widget_TextareaInput',
'widget_attrs' => array(
'cols' => 58,
'rows' => 26,
),
));
$this->fields['comment'] = new Pluf_Form_Field_Varchar(
array('required' => true,
'label' => __('Comment'),
'help_text' => __('One line to describe the changes you made.'),
'initial' => '',
'widget_attrs' => array(
'maxlength' => 200,
'size' => 67,
),
));
if ($this->show_full) {
$tags = $this->page->get_tags_list();
for ($i=1;$i<4;$i++) {
$initial = '';
if (isset($tags[$i-1])) {
if ($tags[$i-1]->class != 'Other') {
$initial = (string) $tags[$i-1];
} else {
$initial = $tags[$i-1]->name;
}
}
$this->fields['label'.$i] = new Pluf_Form_Field_Varchar(
array('required' => false,
'label' => __('Labels'),
'initial' => $initial,
'widget_attrs' => array(
'maxlength' => 50,
'size' => 20,
),
));
}
}
}
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));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
if ($pages->count() > 0 and $pages[0]->id != $this->page->id) {
throw new Pluf_Form_Invalid(__('A page with this title already exists.'));
}
return $title;
}
/**
* Validate the interconnection in the form.
*/
public function clean()
{
if (!$this->show_full) {
return $this->cleaned_data;
}
$conf = new IDF_Conf();
$conf->setProject($this->project);
$onemax = array();
foreach (split(',', $conf->getVal('labels_wiki_one_max', IDF_Form_WikiConf::init_one_max)) as $class) {
if (trim($class) != '') {
$onemax[] = mb_strtolower(trim($class));
}
}
$count = array();
for ($i=1;$i<4;$i++) {
$this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]);
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(mb_strtolower(trim($class)),
trim($name));
} else {
$class = 'other';
$name = $this->cleaned_data['label'.$i];
}
if (!isset($count[$class])) $count[$class] = 1;
else $count[$class] += 1;
if (in_array($class, $onemax) and $count[$class] > 1) {
if (!isset($this->errors['label'.$i])) $this->errors['label'.$i] = array();
$this->errors['label'.$i][] = sprintf(__('You cannot provide more than label from the %s class to a page.'), $class);
throw new Pluf_Form_Invalid(__('You provided an invalid label.'));
}
}
return $this->cleaned_data;
}
/**
* 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.'));
}
if ($this->show_full) {
$tagids = array();
$tags = array();
for ($i=1;$i<4;$i++) {
if (strlen($this->cleaned_data['label'.$i]) > 0) {
if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
list($class, $name) = array(trim($class), trim($name));
} else {
$class = 'Other';
$name = trim($this->cleaned_data['label'.$i]);
}
$tag = IDF_Tag::add($name, $this->project, $class);
$tags[] = $tag;
$tagids[] = $tag->id;
}
}
// Compare between the old and the new data
$changes = array();
$oldtags = $this->page->get_tags_list();
foreach ($tags as $tag) {
if (!Pluf_Model_InArray($tag, $oldtags)) {
if (!isset($changes['lb'])) $changes['lb'] = array();
if ($tag->class != 'Other') {
$changes['lb'][] = (string) $tag; //new tag
} else {
$changes['lb'][] = (string) $tag->name;
}
}
}
foreach ($oldtags as $tag) {
if (!Pluf_Model_InArray($tag, $tags)) {
if (!isset($changes['lb'])) $changes['lb'] = array();
if ($tag->class != 'Other') {
$changes['lb'][] = '-'.(string) $tag; //new tag
} else {
$changes['lb'][] = '-'.(string) $tag->name;
}
}
}
if (trim($this->page->summary) != trim($this->cleaned_data['summary'])) {
$changes['su'] = trim($this->cleaned_data['summary']);
}
// Update the page
$this->page->batchAssoc('IDF_Tag', $tagids);
$this->page->summary = trim($this->cleaned_data['summary']);
$this->page->title = trim($this->cleaned_data['title']);
} else {
$changes = array();
}
$this->page->update();
// add the new revision
$rev = new IDF_WikiRevision();
$rev->wikipage = $this->page;
$rev->content = $this->cleaned_data['content'];
$rev->submitter = $this->user;
$rev->summary = $this->cleaned_data['comment'];
$rev->changes = $changes;
$rev->create();
return $this->page;
}
}

View File

@ -53,6 +53,7 @@ class IDF_Middleware
$request->conf = new IDF_Conf(); $request->conf = new IDF_Conf();
$request->conf->setProject($request->project); $request->conf->setProject($request->project);
$ak = array('downloads_access_rights' => 'hasDownloadsAccess', $ak = array('downloads_access_rights' => 'hasDownloadsAccess',
'wiki_access_rights' => 'hasWikiAccess',
'source_access_rights' => 'hasSourceAccess', 'source_access_rights' => 'hasSourceAccess',
'issues_access_rights' => 'hasIssuesAccess'); 'issues_access_rights' => 'hasIssuesAccess');
$request->rights = array(); $request->rights = array();

View File

@ -0,0 +1,54 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Add the download of files.
*/
function IDF_Migrations_7Wiki_up($params=null)
{
$models = array(
'IDF_WikiPage',
'IDF_WikiRevision',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->createTables();
}
}
function IDF_Migrations_7Wiki_down($params=null)
{
$models = array(
'IDF_WikiRevision',
'IDF_WikiPage',
);
$db = Pluf::db();
$schema = new Pluf_DB_Schema($db);
foreach ($models as $model) {
$schema->model = new $model();
$schema->dropTables();
}
}

View File

@ -40,6 +40,8 @@ function IDF_Migrations_Install_setup($params=null)
'IDF_IssueFile', 'IDF_IssueFile',
'IDF_Commit', 'IDF_Commit',
'IDF_Timeline', 'IDF_Timeline',
'IDF_WikiPage',
'IDF_WikiRevision',
); );
$db = Pluf::db(); $db = Pluf::db();
$schema = new Pluf_DB_Schema($db); $schema = new Pluf_DB_Schema($db);
@ -69,6 +71,8 @@ function IDF_Migrations_Install_teardown($params=null)
$perm = Pluf_Permission::getFromString('IDF.project-owner'); $perm = Pluf_Permission::getFromString('IDF.project-owner');
if ($perm) $perm->delete(); if ($perm) $perm->delete();
$models = array( $models = array(
'IDF_WikiRevision',
'IDF_WikiPage',
'IDF_Timeline', 'IDF_Timeline',
'IDF_IssueFile', 'IDF_IssueFile',
'IDF_Search_Occ', 'IDF_Search_Occ',

View File

@ -143,6 +143,15 @@ class IDF_Precondition
return self::accessTabGeneric($request, 'downloads_access_rights'); return self::accessTabGeneric($request, 'downloads_access_rights');
} }
static public function accessWiki($request)
{
$res = self::baseAccess($request);
if (true !== $res) {
return $res;
}
return self::accessTabGeneric($request, 'wiki_access_rights');
}
/** /**
* Based on the request, it is automatically setting the user. * Based on the request, it is automatically setting the user.
* *

View File

@ -283,10 +283,9 @@ class IDF_Project extends Pluf_Model
/** /**
* Generate the tag clouds. * Generate the tag clouds.
* *
* Return an array of tags sorted class, then name. Each tag get * Return an array of tags sorted by class, then name. Each tag
* the extra property 'nb_use' for the number of use in the * get the extra property 'nb_use' for the number of use in the
* project. For issues, only open issues are used to generate the * project.
* cloud.
* *
* @param string ('issues') 'closed_issues' or 'downloads' * @param string ('issues') 'closed_issues' or 'downloads'
* @return ArrayObject of IDF_Tag * @return ArrayObject of IDF_Tag

View File

@ -32,13 +32,13 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
private $request = null; private $request = null;
private $scm = null; private $scm = null;
function start($text, $request, $echo=true, $wordwrap=true) function start($text, $request, $echo=true, $wordwrap=true, $esc=true)
{ {
$this->project = $request->project; $this->project = $request->project;
$this->request = $request; $this->request = $request;
$this->scm = IDF_Scm::get($request); $this->scm = IDF_Scm::get($request);
if ($wordwrap) $text = wordwrap($text, 69, "\n", true); if ($wordwrap) $text = wordwrap($text, 69, "\n", true);
$text = Pluf_esc($text); if ($esc) $text = Pluf_esc($text);
$text = ereg_replace('[[:alpha:]]+://[^<>[:space:]]+[[:alnum:]/]', $text = ereg_replace('[[:alpha:]]+://[^<>[:space:]]+[[:alnum:]/]',
'<a href="\\0" rel="nofollow">\\0</a>', '<a href="\\0" rel="nofollow">\\0</a>',
$text); $text);

View File

@ -23,80 +23,55 @@
Pluf::loadFunction('Pluf_Text_MarkDown_parse'); Pluf::loadFunction('Pluf_Text_MarkDown_parse');
function IDF_Template_Markdown_filter($mdtext) /**
* Make the links to issues and commits.
*/
class IDF_Template_Markdown extends Pluf_Template_Tag
{ {
$filter = new IDF_Template_Markdown(); private $project = null;
return Pluf_Template::markSafe(Pluf_Text_MarkDown_parse($filter->go($mdtext))); private $request = null;
private $scm = null;
function start($text, $request)
{
$this->project = $request->project;
$this->request = $request;
$filter = new IDF_Template_MarkdownPrefilter();
$text = $filter->go($text);
// The filter has replace < and > also in the code blocks so
// we need to revert them
$tmp = array();
foreach (preg_split("/\015\012|\015|\012/", $text, -1) as $s) {
if (0 === strpos($s, ' ')) {
$s = str_replace(array('&lt;', '&gt;'),
array('<', '>'), $s);
}
$tmp[] = $s;
}
$text = implode("\n", $tmp);
// Replace like in the issue text
$tag = new IDF_Template_IssueComment();
$text = $tag->start($text, $request, false, false, false);
// Replace [[PageName]] with corresponding link to the page.
// if not the right to see the
$text = preg_replace_callback('#\[\[([A-Za-z0-9\-]+)\]\]#im',
array($this, 'callbackWikiPage'),
$text);
echo Pluf_Text_MarkDown_parse($text);
} }
/** function callbackWikiPage($m)
* Strict class to only allow entities.
*/
class IDF_Template_Markdown extends Pluf_Text_HTML_Filter
{ {
public $allowed = array(); $sql = new Pluf_SQL('project=%s AND title=%s',
public $always_close = array(); array($this->project->id, $m[1]));
public $remove_blanks = array(); $pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
public $allowed_entities = array( if ($pages->count() != 1) {
'amp', return $m[0];
'gt',
'lt',
'quot',
'nbsp',
'ndash',
'rdquo',
'ldquo',
'Alpha',
'Beta',
'Gamma',
'Delta',
'Epsilon',
'Zeta',
'Eta',
'Theta',
'Iota',
'Kappa',
'Lambda',
'Mu',
'Nu',
'Xi',
'Omicron',
'Pi',
'Rho',
'Sigma',
'Tau',
'Upsilon',
'Phi',
'Chi',
'Psi',
'Omega',
'alpha',
'beta',
'gamma',
'delta',
'epsilon',
'zeta',
'eta',
'theta',
'iota',
'kappa',
'lambda',
'mu',
'nu',
'xi',
'omicron',
'pi',
'rho',
'sigmaf',
'sigma',
'tau',
'upsilon',
'phi',
'chi',
'psi',
'omega',
'thetasym',
'upsih',
'piv',
);
} }
if (!$this->request->rights['hasWikiAccess']) {
return $m[1];
}
return '<a href="'.Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view', array($this->project->shortname, $pages[0]->title)).'" title="'.Pluf_esc($pages[0]->summary).'">'.$m[1].'</a>';
}
}

View File

@ -0,0 +1,94 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Strict class to only allow entities.
*/
class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
{
public $allowed = array();
public $always_close = array();
public $remove_blanks = array();
public $allowed_entities = array(
'amp',
'gt',
'lt',
'quot',
'nbsp',
'ndash',
'rdquo',
'ldquo',
'Alpha',
'Beta',
'Gamma',
'Delta',
'Epsilon',
'Zeta',
'Eta',
'Theta',
'Iota',
'Kappa',
'Lambda',
'Mu',
'Nu',
'Xi',
'Omicron',
'Pi',
'Rho',
'Sigma',
'Tau',
'Upsilon',
'Phi',
'Chi',
'Psi',
'Omega',
'alpha',
'beta',
'gamma',
'delta',
'epsilon',
'zeta',
'eta',
'theta',
'iota',
'kappa',
'lambda',
'mu',
'nu',
'xi',
'omicron',
'pi',
'rho',
'sigmaf',
'sigma',
'tau',
'upsilon',
'phi',
'chi',
'psi',
'omega',
'thetasym',
'upsih',
'piv',
);
}

View File

@ -81,6 +81,10 @@ class IDF_Views_Project
if (true === IDF_Precondition::accessDownloads($request)) { if (true === IDF_Precondition::accessDownloads($request)) {
$rights[] = '\'IDF_Upload\''; $rights[] = '\'IDF_Upload\'';
} }
if (true === IDF_Precondition::accessWiki($request)) {
$rights[] = '\'IDF_WikiPage\'';
$rights[] = '\'IDF_WikiRevision\'';
}
if (count($rights) == 0) { if (count($rights) == 0) {
$rights[] = '\'IDF_Dummy\''; $rights[] = '\'IDF_Dummy\'';
} }
@ -233,6 +237,49 @@ class IDF_Views_Project
$request); $request);
} }
/**
* Administrate the information pages of a project.
*/
public $adminWiki_precond = array('IDF_Precondition::projectOwner');
public function adminWiki($request, $match)
{
$prj = $request->project;
$title = sprintf(__('%s Documentation Configuration'), (string) $prj);
$conf = new IDF_Conf();
$conf->setProject($prj);
if ($request->method == 'POST') {
$form = new IDF_Form_WikiConf($request->POST);
if ($form->isValid()) {
foreach ($form->cleaned_data as $key=>$val) {
$conf->setVal($key, $val);
}
$request->user->setMessage(__('The documentation configuration has been saved.'));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Project::adminDownloads',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$params = array();
$keys = array('labels_wiki_predefined', 'labels_wiki_one_max');
foreach ($keys as $key) {
$_val = $conf->getVal($key, false);
if ($_val !== false) {
$params[$key] = $_val;
}
}
if (count($params) == 0) {
$params = null; //Nothing in the db, so new form.
}
$form = new IDF_Form_WikiConf($params);
}
return Pluf_Shortcuts_RenderToResponse('idf/admin/wiki.html',
array(
'page_title' => $title,
'form' => $form,
),
$request);
}
/** /**
* Administrate the members of a project. * Administrate the members of a project.
*/ */
@ -292,7 +339,8 @@ class IDF_Views_Project
} else { } else {
$params = array(); $params = array();
$keys = array('downloads_access_rights', 'source_access_rights', $keys = array('downloads_access_rights', 'source_access_rights',
'issues_access_rights', 'private_project'); 'issues_access_rights', 'private_project',
'wiki_access_rights');
foreach ($keys as $key) { foreach ($keys as $key) {
$_val = $request->conf->getVal($key, false); $_val = $request->conf->getVal($key, false);
if ($_val !== false) { if ($_val !== false) {

279
src/IDF/Views/Wiki.php Normal file
View File

@ -0,0 +1,279 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
Pluf::loadFunction('Pluf_Shortcuts_RenderToResponse');
Pluf::loadFunction('Pluf_Shortcuts_GetObjectOr404');
Pluf::loadFunction('Pluf_Shortcuts_GetFormForModel');
/**
* Documentation pages views.
*/
class IDF_Views_Wiki
{
/**
* View list of issues for a given project.
*/
public $index_precond = array('IDF_Precondition::accessWiki');
public function index($request, $match, $api=false)
{
$prj = $request->project;
$title = sprintf(__('%s Documentation'), (string) $prj);
// Paginator to paginate the pages
$pag = new Pluf_Paginator(new IDF_WikiPage());
$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 documentation pages.');
$pag->action = array('IDF_Views_Wiki::index', array($prj->shortname));
$pag->edit_action = array('IDF_Views_Wiki::view', 'shortname', 'title');
$sql = 'project=%s';
$ptags = self::getWikiTags($prj);
$dtag = array_pop($ptags); // The last tag is the deprecated tag.
$ids = self::getDeprecatedPagesIds($prj, $dtag);
if (count($ids)) {
$sql .= ' AND id NOT IN ('.implode(',', $ids).')';
}
$pag->forced_where = new Pluf_SQL($sql, array($prj->id));
$list_display = array(
'title' => __('Page Title'),
array('summary', 'IDF_Views_Wiki_SummaryAndLabels', __('Summary')),
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 documentation pages were found.');
$pag->sort_order = array('title', 'ASC');
$pag->setFromRequest($request);
//$tags = $prj->getTagCloud('downloads');
return Pluf_Shortcuts_RenderToResponse('idf/wiki/index.html',
array(
'page_title' => $title,
'pages' => $pag,
//'tags' => $tags,
'deprecated' => count($ids),
'dlabel' => $dtag,
),
$request);
}
/**
* Create a new documentation page.
*/
public $create_precond = array('IDF_Precondition::accessWiki',
'Pluf_Precondition::loginRequired');
public function create($request, $match)
{
$prj = $request->project;
$title = __('New Page');
$preview = false;
if ($request->method == 'POST') {
$form = new IDF_Form_WikiCreate($request->POST,
array('project' => $prj,
'user' => $request->user
));
if ($form->isValid() and !isset($request->POST['preview'])) {
$page = $form->save();
$urlpage = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
array($prj->shortname, $page->title));
$request->user->setMessage(sprintf(__('The page <a href="%s">%s</a> has been created.'), $urlpage, Pluf_esc($page->title)));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::index',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
} elseif (isset($request->POST['preview'])) {
$preview = $request->POST['content'];
}
} else {
$form = new IDF_Form_WikiCreate(null,
array('project' => $prj,
'user' => $request->user));
}
return Pluf_Shortcuts_RenderToResponse('idf/wiki/create.html',
array(
'auto_labels' => self::autoCompleteArrays($prj),
'page_title' => $title,
'form' => $form,
'preview' => $preview,
),
$request);
}
/**
* View a documentation page.
*/
public $view_precond = array('IDF_Precondition::accessWiki');
public function view($request, $match)
{
$prj = $request->project;
// Find the page
$sql = new Pluf_SQL('project=%s AND title=%s',
array($prj->id, $match[2]));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
if ($pages->count() != 1) {
throw new Pluf_HTTP_Error404($request);
}
$page = $pages[0];
$title = $page->title;
$revision = $page->get_current_revision();
return Pluf_Shortcuts_RenderToResponse('idf/wiki/view.html',
array(
'page_title' => $title,
'page' => $page,
'rev' => $revision,
'tags' => $page->get_tags_list(),
),
$request);
}
/**
* View a documentation page.
*/
public $update_precond = array('IDF_Precondition::accessWiki',
'Pluf_Precondition::loginRequired');
public function update($request, $match)
{
$prj = $request->project;
// Find the page
$sql = new Pluf_SQL('project=%s AND title=%s',
array($prj->id, $match[2]));
$pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
if ($pages->count() != 1) {
throw new Pluf_HTTP_Error404($request);
}
$page = $pages[0];
$title = sprintf(__('Update %s'), $page->title);
$revision = $page->get_current_revision();
$preview = false;
$params = array('project' => $prj,
'user' => $request->user,
'page' => $page);
if ($request->method == 'POST') {
$form = new IDF_Form_WikiUpdate($request->POST, $params);
if ($form->isValid() and !isset($request->POST['preview'])) {
$page = $form->save();
$urlpage = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
array($prj->shortname, $page->title));
$request->user->setMessage(sprintf(__('The page <a href="%s">%s</a> has been updated.'), $urlpage, Pluf_esc($page->title)));
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::index',
array($prj->shortname));
return new Pluf_HTTP_Response_Redirect($url);
} elseif (isset($request->POST['preview'])) {
$preview = $request->POST['content'];
}
} else {
$form = new IDF_Form_WikiUpdate(null, $params);
}
return Pluf_Shortcuts_RenderToResponse('idf/wiki/update.html',
array(
'auto_labels' => self::autoCompleteArrays($prj),
'page_title' => $title,
'page' => $page,
'rev' => $revision,
'form' => $form,
'preview' => $preview,
),
$request);
}
/**
* Get the wiki tags.
*
* @param IDF_Project
* @return ArrayObject The tags
*/
public static function getWikiTags($project)
{
return $project->getTagsFromConfig('labels_wiki_predefined',
IDF_Form_WikiConf::init_predefined);
}
/**
* Get deprecated page ids.
*
* @param IDF_Project
* @param IDF_Tag Deprecated tag (null)
* @return array Ids of the deprecated pages.
*/
public static function getDeprecatedPagesIds($project, $dtag=null)
{
if (is_null($dtag)) {
$ptags = self::getDownloadTags($project);
$dtag = array_pop($ptags); // The last tag is the deprecated tag
}
$sql = new Pluf_SQL('project=%s AND idf_tag_id=%s', array($project->id,
$dtag->id));
$ids = array();
foreach (Pluf::factory('IDF_WikiPage')->getList(array('filter' => $sql->gen(), 'view' => 'join_tags'))
as $file) {
$ids[] = (int) $file->id;
}
return $ids;
}
/**
* Create the autocomplete arrays for the little AJAX stuff.
*/
public static function autoCompleteArrays($project)
{
$conf = new IDF_Conf();
$conf->setProject($project);
$st = preg_split("/\015\012|\015|\012/",
$conf->getVal('labels_wiki_predefined', IDF_Form_UploadConf::init_predefined), -1, PREG_SPLIT_NO_EMPTY);
$auto = '';
foreach ($st as $s) {
$v = '';
$d = '';
$_s = split('=', $s, 2);
if (count($_s) > 1) {
$v = trim($_s[0]);
$d = trim($_s[1]);
} else {
$v = trim($_s[0]);
}
$auto .= sprintf('{ name: "%s", to: "%s" }, ',
Pluf_esc($d), Pluf_esc($v));
}
return substr($auto, 0, -1);
}
}
/**
* Display the summary of a page, then on a new line, display the
* list of labels.
*/
function IDF_Views_Wiki_SummaryAndLabels($field, $page, $extra='')
{
$tags = array();
foreach ($page->get_tags_list() as $tag) {
$tags[] = Pluf_esc((string) $tag);
}
$out = '';
if (count($tags)) {
$out = '<br /><span class="note label">'.implode(', ', $tags).'</span>';
}
return Pluf_esc($page->summary).$out;
}

192
src/IDF/WikiPage.php Normal file
View File

@ -0,0 +1,192 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
Pluf::loadFunction('Pluf_HTTP_URL_urlForView');
Pluf::loadFunction('Pluf_Template_dateAgo');
/**
* Base definition of a wiki page.
*
* A wiki page can have tags and be starred by the users. The real
* content of the page is stored in the IDF_WikiRevision
* object. Several revisions are associated to a given page.
*/
class IDF_WikiPage extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_wikipages';
$this->_a['model'] = __CLASS__;
$this->_a['cols'] = array(
// It is mandatory to have an "id" column.
'id' =>
array(
'type' => 'Pluf_DB_Field_Sequence',
'blank' => true,
),
'project' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_Project',
'blank' => false,
'verbose' => __('project'),
'relate_name' => 'wikipages',
),
'title' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 250,
'verbose' => __('title'),
'help_text' => __('The title of the page must only contain letters, digits or the dash character. For example: My-new-Wiki-Page.'),
),
'summary' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 250,
'verbose' => __('summary'),
'help_text' => __('A one line description of the page content.'),
),
'submitter' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_User',
'blank' => false,
'verbose' => __('submitter'),
'relate_name' => 'submitted_wikipages',
),
'interested' =>
array(
'type' => 'Pluf_DB_Field_Manytomany',
'model' => 'Pluf_User',
'blank' => true,
'verbose' => __('interested users'),
'help_text' => 'Interested users will get an email notification when the wiki page is changed.',
),
'tags' =>
array(
'type' => 'Pluf_DB_Field_Manytomany',
'blank' => true,
'model' => 'IDF_Tag',
'verbose' => __('labels'),
),
'creation_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('creation date'),
),
'modif_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('modification date'),
),
);
$this->_a['idx'] = array(
'modif_dtime_idx' =>
array(
'col' => 'modif_dtime',
'type' => 'normal',
),
);
$table = $this->_con->pfx.'idf_tag_idf_wikipage_assoc';
$this->_a['views'] = array(
'join_tags' =>
array(
'join' => 'LEFT JOIN '.$table
.' ON idf_wikipage_id=id',
),
);
}
function __toString()
{
return $this->title.' - '.$this->summary;
}
function _toIndex()
{
$rev = $this->get_current_revision()->_toIndex();
$str = str_repeat($this->summary.' ', 4).' '.$rev;
return Pluf_Text::cleanString(html_entity_decode($str, ENT_QUOTES, 'UTF-8'));
}
function get_current_revision()
{
$db = $this->getDbConnection();
$true = Pluf_DB_BooleanToDb(true, $db);
$rev = $this->get_revisions_list(array('filter' => 'is_head='.$true,
'nb' => 1));
return ($rev->count() == 1) ? $rev[0] : null;
}
function preSave($create=false)
{
if ($this->id == '') {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
}
$this->modif_dtime = gmdate('Y-m-d H:i:s');
}
function postSave($create=false)
{
// Note: No indexing is performed here. The indexing is
// triggered in the postSave step of the revision to ensure
// that the page as a given revision in the database when
// doing the indexing.
if ($create) {
IDF_Timeline::insert($this, $this->get_project(),
$this->get_submitter());
}
}
/**
* Returns an HTML fragment used to display this wikipage in the
* timeline.
*
* The request object is given to be able to check the rights and
* as such create links to other items etc. You can consider that
* if displayed, you can create a link to it.
*
* @param Pluf_HTTP_Request
* @return Pluf_Template_SafeString
*/
public function timelineFragment($request)
{
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
array($request->project->shortname,
$this->title));
$out = '<tr class="log"><td><a href="'.$url.'">'.
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
'</a></td><td>';
$submitter = $this->get_submitter();
$out .= sprintf(__('<a href="%1$s" title="View page">%2$s</a>, %3$s'), $url, Pluf_esc($this->title), Pluf_esc($this->summary)).'</td>';
$out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Creation of <a href="%s">page&nbsp;%s</a>'), $url, Pluf_esc($this->title)).', '.__('by').' '.Pluf_esc($submitter).'</div></td></tr>';
return Pluf_Template::markSafe($out);
}
}

183
src/IDF/WikiRevision.php Normal file
View File

@ -0,0 +1,183 @@
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 Céondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* A revision of a wiki page.
*
*/
class IDF_WikiRevision extends Pluf_Model
{
public $_model = __CLASS__;
function init()
{
$this->_a['table'] = 'idf_wikirevisions';
$this->_a['model'] = __CLASS__;
$this->_a['cols'] = array(
// It is mandatory to have an "id" column.
'id' =>
array(
'type' => 'Pluf_DB_Field_Sequence',
'blank' => true,
),
'wikipage' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'IDF_WikiPage',
'blank' => false,
'verbose' => __('page'),
'relate_name' => 'revisions',
),
'is_head' =>
array(
'type' => 'Pluf_DB_Field_Boolean',
'blank' => false,
'default' => false,
'help_text' => 'If this revision is the latest, we mark it as being the head revision.',
'index' => true,
),
'summary' =>
array(
'type' => 'Pluf_DB_Field_Varchar',
'blank' => false,
'size' => 250,
'verbose' => __('summary'),
'help_text' => __('A one line description of the changes.'),
),
'content' =>
array(
'type' => 'Pluf_DB_Field_Compressed',
'blank' => false,
'verbose' => __('content'),
),
'submitter' =>
array(
'type' => 'Pluf_DB_Field_Foreignkey',
'model' => 'Pluf_User',
'blank' => false,
'verbose' => __('submitter'),
),
'changes' =>
array(
'type' => 'Pluf_DB_Field_Serialized',
'blank' => true,
'verbose' => __('changes'),
'help_text' => 'Serialized array of the changes in the issue.',
),
'creation_dtime' =>
array(
'type' => 'Pluf_DB_Field_Datetime',
'blank' => true,
'verbose' => __('creation date'),
),
);
$this->_a['idx'] = array(
'creation_dtime_idx' =>
array(
'col' => 'creation_dtime',
'type' => 'normal',
),
);
}
function changedRevision()
{
return (is_array($this->changes) and count($this->changes) > 0);
}
function _toIndex()
{
return $this->content;
}
function preSave($create=false)
{
if ($this->id == '') {
$this->creation_dtime = gmdate('Y-m-d H:i:s');
$this->is_head = true;
}
}
function postSave($create=false)
{
if ($create) {
// Check if more than one revision for this page. We do
// not want to insert the first revision in the timeline
// as the page itself is inserted. We do not insert on
// update as update is performed to change the is_head
// flag.
$sql = new Pluf_SQL('wikipage=%s', array($this->wikipage));
$rev = Pluf::factory('IDF_WikiRevision')->getList(array('filter'=>$sql->gen()));
if ($rev->count() > 1) {
IDF_Timeline::insert($this, $this->get_wikipage()->get_project(),
$this->get_submitter());
foreach ($rev as $r) {
if ($r->id != $this->id and $r->is_head) {
$r->is_head = false;
$r->update();
}
}
}
$page = $this->get_wikipage();
$page->update(); // Will update the modification timestamp.
IDF_Search::index($page);
}
}
public function timelineFragment($request)
{
$page = $this->get_wikipage();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
array($request->project->shortname,
$page->title));
$out = "\n".'<tr class="log"><td><a href="'.$url.'">'.
Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')).
'</a></td><td>';
$submitter = $this->get_submitter();
$out .= sprintf(__('<a href="%1$s" title="View page">%2$s</a>, %3$s'), $url, Pluf_esc($page->title), Pluf_esc($this->summary));
//$out .= '<strong>'.__('Summary:').'</strong>&nbsp;'.Pluf_esc($this->summary).' ';
if ($this->changedRevision()) {
$out .= '<div class="issue-changes-timeline">';
foreach ($this->changes as $w => $v) {
$out .= '<strong>';
switch ($w) {
case 'lb':
$out .= __('Labels:'); break;
}
$out .= '</strong>&nbsp;';
if ($w == 'lb') {
$out .= Pluf_esc(implode(', ', $v));
} else {
$out .= Pluf_esc($v);
}
$out .= ' ';
}
$out .= '</div>';
}
$out .= '</td></tr>';
$out .= "\n".'<tr class="extra"><td colspan="2">
<div class="helptext right">'.sprintf(__('Change of <a href="%s">%s</a>'), $url, Pluf_esc($page->title)).', '.__('by').' '.Pluf_esc($submitter).'</div></td></tr>';
return Pluf_Template::markSafe($out);
}
}

View File

@ -161,10 +161,10 @@ $cfg['template_tags'] = array(
'hotkey' => 'IDF_Template_HotKey', 'hotkey' => 'IDF_Template_HotKey',
'issuetext' => 'IDF_Template_IssueComment', 'issuetext' => 'IDF_Template_IssueComment',
'timeline' => 'IDF_Template_TimelineFragment', 'timeline' => 'IDF_Template_TimelineFragment',
'markdown' => 'IDF_Template_Markdown',
); );
$cfg['template_modifiers'] = array( $cfg['template_modifiers'] = array(
'size' => 'IDF_Views_Source_PrettySize', 'size' => 'IDF_Views_Source_PrettySize',
'markdown' => 'IDF_Template_Markdown_filter',
); );
// available languages // available languages

View File

@ -147,37 +147,37 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/attachment/(\d+)/(.*)$#',
// ---------- SCM ---------------------------------------- // ---------- SCM ----------------------------------------
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/(\w+)/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/([\S^/]+)/$#',
'base' => $base, 'base' => $base,
'priority' => 4, 'priority' => 4,
'model' => 'IDF_Views_Source', 'model' => 'IDF_Views_Source',
'method' => 'treeBase'); 'method' => 'treeBase');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/(\w+)/(.*)$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/([\S^/]+)/(.*)$#',
'base' => $base, 'base' => $base,
'priority' => 4, 'priority' => 4,
'model' => 'IDF_Views_Source', 'model' => 'IDF_Views_Source',
'method' => 'tree'); 'method' => 'tree');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changes/(\w+)/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changes/([\S^/]+)/$#',
'base' => $base, 'base' => $base,
'priority' => 4, 'priority' => 4,
'model' => 'IDF_Views_Source', 'model' => 'IDF_Views_Source',
'method' => 'changeLog'); 'method' => 'changeLog');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/commit/(\w+)/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/commit/([\S^/]+)/$#',
'base' => $base, 'base' => $base,
'priority' => 4, 'priority' => 4,
'model' => 'IDF_Views_Source', 'model' => 'IDF_Views_Source',
'method' => 'commit'); 'method' => 'commit');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/download/(\w+)/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/download/([\S^/]+)/$#',
'base' => $base, 'base' => $base,
'priority' => 4, 'priority' => 4,
'model' => 'IDF_Views_Source', 'model' => 'IDF_Views_Source',
'method' => 'download'); 'method' => 'download');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/file/(\w+)/(.*)$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/file/([\S^/]+)/(.*)$#',
'base' => $base, 'base' => $base,
'priority' => 4, 'priority' => 4,
'model' => 'IDF_Views_Source', 'model' => 'IDF_Views_Source',
@ -195,6 +195,32 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changesrev/$#',
'model' => 'IDF_Views_Source_Svn', 'model' => 'IDF_Views_Source_Svn',
'method' => 'changelogRev'); 'method' => 'changelogRev');
// ---------- WIKI -----------------------------------------
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Wiki',
'method' => 'index');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/create/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Wiki',
'method' => 'create');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/update/(.*)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Wiki',
'method' => 'update');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/page/(.*)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Wiki',
'method' => 'view');
// ---------- Downloads ------------------------------------ // ---------- Downloads ------------------------------------
$ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/$#',
@ -254,6 +280,12 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/downloads/$#',
'model' => 'IDF_Views_Project', 'model' => 'IDF_Views_Project',
'method' => 'adminDownloads'); 'method' => 'adminDownloads');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/wiki/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'adminWiki');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/source/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/source/$#',
'base' => $base, 'base' => $base,
'priority' => 4, 'priority' => 4,

View File

@ -30,5 +30,8 @@ $m['IDF_IssueFile'] = array('relate_to' => array('IDF_IssueComment', 'Pluf_User'
$m['IDF_Upload'] = array('relate_to' => array('IDF_Project', 'Pluf_User'), $m['IDF_Upload'] = array('relate_to' => array('IDF_Project', 'Pluf_User'),
'relate_to_many' => array('IDF_Tag')); 'relate_to_many' => array('IDF_Tag'));
$m['IDF_Search_Occ'] = array('relate_to' => array('IDF_Project'),); $m['IDF_Search_Occ'] = array('relate_to' => array('IDF_Project'),);
$m['IDF_WikiPage'] = array('relate_to' => array('IDF_Project', 'Pluf_User'),
'relate_to_many' => array('IDF_Tag', 'Pluf_User'));
$m['IDF_WikiRevision'] = array('relate_to' => array('IDF_WikiPage', 'Pluf_User'));
return $m; return $m;

View File

@ -3,8 +3,9 @@
{block subtabs} {block subtabs}
<div id="sub-tabs"> <div id="sub-tabs">
<a {if $inSummary}class="active" {/if}href="{url 'IDF_Views_Project::admin', array($project.shortname)}">{trans 'Project Summary'}</a> | <a {if $inSummary}class="active" {/if}href="{url 'IDF_Views_Project::admin', array($project.shortname)}">{trans 'Project Summary'}</a> |
<a {if $inIssueTracking}class="active" {/if}href="{url 'IDF_Views_Project::adminIssues', array($project.shortname)}">{trans 'Issue Tracking'}</a> |
<a {if $inDownloads}class="active" {/if}href="{url 'IDF_Views_Project::adminDownloads', array($project.shortname)}">{trans 'Downloads'}</a> | <a {if $inDownloads}class="active" {/if}href="{url 'IDF_Views_Project::adminDownloads', array($project.shortname)}">{trans 'Downloads'}</a> |
<a {if $inWiki}class="active" {/if}href="{url 'IDF_Views_Project::adminWiki', array($project.shortname)}">{trans 'Documentation'}</a> |
<a {if $inIssueTracking}class="active" {/if}href="{url 'IDF_Views_Project::adminIssues', array($project.shortname)}">{trans 'Issue Tracking'}</a> |
<a {if $inSource}class="active" {/if}href="{url 'IDF_Views_Project::adminSource', array($project.shortname)}">{trans 'Source'}</a> | <a {if $inSource}class="active" {/if}href="{url 'IDF_Views_Project::adminSource', array($project.shortname)}">{trans 'Source'}</a> |
<a {if $inMembers}class="active" {/if}href="{url 'IDF_Views_Project::adminMembers', array($project.shortname)}">{trans 'Project Members'}</a> | <a {if $inMembers}class="active" {/if}href="{url 'IDF_Views_Project::adminMembers', array($project.shortname)}">{trans 'Project Members'}</a> |
<a {if $inTabs}class="active" {/if}href="{url 'IDF_Views_Project::adminTabs', array($project.shortname)}">{trans 'Tabs Access'}</a> <a {if $inTabs}class="active" {/if}href="{url 'IDF_Views_Project::adminTabs', array($project.shortname)}">{trans 'Tabs Access'}</a>

View File

@ -18,6 +18,12 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<th><strong>{$form.f.wiki_access_rights.labelTag}:</strong></th>
<td>{if $form.f.wiki_access_rights.errors}{$form.f.wiki_access_rights.fieldErrors}{/if}
{$form.f.wiki_access_rights|unsafe}
</td>
</tr>
<tr>
<th><strong>{$form.f.issues_access_rights.labelTag}:</strong></th> <th><strong>{$form.f.issues_access_rights.labelTag}:</strong></th>
<td>{if $form.f.issues_access_rights.errors}{$form.f.issues_access_rights.fieldErrors}{/if} <td>{if $form.f.issues_access_rights.errors}{$form.f.issues_access_rights.fieldErrors}{/if}
{$form.f.issues_access_rights|unsafe} {$form.f.issues_access_rights|unsafe}

View File

@ -0,0 +1,34 @@
{extends "idf/admin/base.html"}
{block docclass}yui-t1{assign $inWiki = true}{/block}
{block body}
<form method="post" action=".">
<table class="form" summary="">
<tr>
<td colspan="2"><strong>{$form.f.labels_wiki_predefined.labelTag}:</strong><br />
{if $form.f.labels_wiki_predefined.errors}{$form.f.labels_wiki_predefined.fieldErrors}{/if}
{$form.f.labels_wiki_predefined|unsafe}
</td>
</tr>
<tr>
<td colspan="2">{$form.f.labels_wiki_one_max.labelTag}:<br />
{if $form.f.labels_wiki_one_max.errors}{$form.f.labels_wiki_one_max.fieldErrors}{/if}
{$form.f.labels_wiki_one_max|unsafe}
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="{trans 'Save Changes'}" name="submit" />
</td>
</tr>
</table>
</form>
{/block}
{block context}
<div class="issue-submit-info">
{blocktrans}
<p><strong>Instructions:</strong></p>
<p>List one status value per line in desired sort-order.</p>
<p>Optionally, use an equals-sign to document the meaning of each status value.</p>
{/blocktrans}
</div>
{/block}

View File

@ -43,8 +43,9 @@
<div id="main-tabs"> <div id="main-tabs">
{if $project} {if $project}
<a accesskey="1" href="{url 'IDF_Views_Project::home', array($project.shortname)}"{block tabhome}{/block}>{trans 'Project Home'}</a> <a accesskey="1" href="{url 'IDF_Views_Project::home', array($project.shortname)}"{block tabhome}{/block}>{trans 'Project Home'}</a>
{if $hasIssuesAccess} <a href="{url 'IDF_Views_Issue::index', array($project.shortname)}"{block tabissues}{/block}>{trans 'Issues'}</a>{/if}
{if $hasDownloadsAccess} <a href="{url 'IDF_Views_Download::index', array($project.shortname)}"{block tabdownloads}{/block}>{trans 'Downloads'}</a>{/if} {if $hasDownloadsAccess} <a href="{url 'IDF_Views_Download::index', array($project.shortname)}"{block tabdownloads}{/block}>{trans 'Downloads'}</a>{/if}
{if $hasWikiAccess} <a href="{url 'IDF_Views_Wiki::index', array($project.shortname)}"{block tabwiki}{/block}>{trans 'Documentation'}</a>{/if}
{if $hasIssuesAccess} <a href="{url 'IDF_Views_Issue::index', array($project.shortname)}"{block tabissues}{/block}>{trans 'Issues'}</a>{/if}
{if $hasSourceAccess} <a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}"{block tabsource}{/block}>{trans 'Source'}</a>{/if} {if $hasSourceAccess} <a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}"{block tabsource}{/block}>{trans 'Source'}</a>{/if}
{if $isOwner} {if $isOwner}
<a href="{url 'IDF_Views_Project::admin', array($project.shortname)}"{block tabadmin}{/block}>{trans 'Administer'}</a>{/if}{/if} <a href="{url 'IDF_Views_Project::admin', array($project.shortname)}"{block tabadmin}{/block}>{trans 'Administer'}</a>{/if}{/if}

View File

@ -7,7 +7,7 @@
</div> </div>
{/block} {/block}
{block body} {block body}
{$project.description|markdown} {markdown $project.description, $request}
{/block} {/block}
{block context} {block context}
{if count($downloads) > 0} {if count($downloads) > 0}

View File

@ -0,0 +1,10 @@
{extends "idf/base.html"}
{block tabwiki} class="active"{/block}
{block subtabs}
<div id="sub-tabs">
<a {if $inWiki}class="active" {/if}href="{url 'IDF_Views_Wiki::index', array($project.shortname)}">{trans 'List Pages'}</a>
{if !$user.isAnonymous()} | <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Wiki::create', array($project.shortname)}">{trans 'New Page'}</a> {/if}
{if !$user.isAnonymous() and $inView} | <a href="{url 'IDF_Views_Wiki::update', array($project.shortname, $page.title)}">{trans 'Update This Page'}</a> {/if}
{superblock}
</div>
{/block}

View File

@ -0,0 +1,68 @@
{extends "idf/wiki/base.html"}
{block docclass}yui-t1{assign $inCreate = true}{/block}
{block body}
{if $preview}
<h2 class="top">{trans 'Preview of the Page'}</h2>
{markdown $preview, $request}
{/if}
{if $form.errors}
<div class="px-message-error">
<p>{trans 'The form contains some errors. Please correct them to create the page.'}</p>
{if $form.get_top_errors}
{$form.render_top_errors|unsafe}
{/if}
</div>
{/if}
<form method="post" enctype="multipart/form-data" action="." >
<table class="form" summary="">
<tr>
<th><strong>{$form.f.title.labelTag}:</strong></th>
<td>{if $form.f.title.errors}{$form.f.title.fieldErrors}{/if}
{$form.f.title|unsafe}<br />
<span class="helptext">{$form.f.title.help_text}</span>
</td>
</tr>
<tr>
<th><strong>{$form.f.summary.labelTag}:</strong></th>
<td>{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if}
{$form.f.summary|unsafe}<br />
<span class="helptext">{$form.f.summary.help_text}</span>
</td>
</tr>
<tr>
<th><strong>{$form.f.content.labelTag}:</strong></th>
<td>{if $form.f.content.errors}{$form.f.content.fieldErrors}{/if}
{$form.f.content|unsafe}
</td>
</tr>{if $isOwner or $isMember}
<tr>
<th>{$form.f.label1.labelTag}:</th>
<td>
{if $form.f.label1.errors}{$form.f.label1.fieldErrors}{/if}{$form.f.label1|unsafe}
{if $form.f.label2.errors}{$form.f.label2.fieldErrors}{/if}{$form.f.label2|unsafe}
{if $form.f.label3.errors}{$form.f.label3.fieldErrors}{/if}{$form.f.label3|unsafe}
</td>
</tr>{/if}
<tr>
<td>&nbsp;</td>
<td><input type="submit" value="{trans 'Preview'}" name="preview" /> &nbsp; <input type="submit" value="{trans 'Create Page'}" name="submit" /> | <a href="{url 'IDF_Views_Wiki::index', array($project.shortname)}">{trans 'Cancel'}</a>
</td>
</tr>
</table>
</form>
{/block}
{block context}
<div class="issue-submit-info">
{include 'idf/wiki/edit-info.html'}
</div>
{/block}
{block javascript}
<script type="text/javascript">
document.getElementById('id_title').focus()
</script>
{include 'idf/wiki/js-autocomplete.html'}{/block}

View File

@ -0,0 +1,6 @@
{assign $url = 'http://daringfireball.net/projects/markdown/syntax'}
{blocktrans}
<p><strong>Instructions:</strong></p>
<p>The content of the page can use the <a href="{$url}">Markdown syntax</a>.</p>
<p>Website addresses are automatically linked and you can link to another page in the documentation using double square brackets like that [[AnotherPage]].</p>
{/blocktrans}

View File

@ -0,0 +1,13 @@
{extends "idf/wiki/base.html"}
{block docclass}yui-t1{assign $inWiki=true}{/block}
{block body}
{$pages.render}
{if !$user.isAnonymous()}
{aurl 'url', 'IDF_Views_Wiki::create', array($project.shortname)}
<p><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/add.png'}" alt="+" align="bottom" /></a> <a href="{$url}">{trans 'New Page'}</a></p>{/if}
{/block}
{block context}
<p><strong>{trans 'Number of pages:'}</strong> {$pages.nb_items}</p>
{/block}

View File

@ -0,0 +1,27 @@
{if $isOwner or $isMember}
<script type="text/javascript" src="{media '/idf/js/jquery.bgiframe.min.js'}"></script>
<script type="text/javascript" src="{media '/idf/js/jquery.autocomplete.min.js'}"></script>
<script type="text/javascript" charset="utf-8">
// <!-- {literal}
$(document).ready(function(){
var auto_labels = [{/literal}{$auto_labels|safe}{literal}];
var j=0;
for (j=1;j<4;j=j+1) {
$("#id_label"+j).autocomplete(auto_labels, {
minChars: 0,
width: 310,
matchContains: true,
max: 50,
highlightItem: false,
formatItem: function(row, i, max, term) {
return row.to.replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + " <span style='font-size: 80%;'>" + row.name + "</span>";
},
formatResult: function(row) {
return row.to;
}
});
}
});
{/literal} //-->
</script>
{/if}

View File

@ -0,0 +1,72 @@
{extends "idf/wiki/base.html"}
{block docclass}yui-t2{/block}
{block body}
{if $preview}
<h2 class="top">{trans 'Preview of the Page'}</h2>
{markdown $preview, $request}
{/if}
{if $form.errors}
<div class="px-message-error">
<p>{trans 'The form contains some errors. Please correct them to update the page.'}</p>
{if $form.get_top_errors}
{$form.render_top_errors|unsafe}
{/if}
</div>
{/if}
<form method="post" enctype="multipart/form-data" action="." >
<table class="form" summary="">{if $isOwner or $isMember}
<tr>
<th><strong>{$form.f.title.labelTag}:</strong></th>
<td>{if $form.f.title.errors}{$form.f.title.fieldErrors}{/if}
{$form.f.title|unsafe}<br />
<span class="helptext">{$form.f.title.help_text}</span>
</td>
</tr>
<tr>
<th><strong>{$form.f.summary.labelTag}:</strong></th>
<td>{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if}
{$form.f.summary|unsafe}<br />
<span class="helptext">{$form.f.summary.help_text}</span>
</td>
</tr>{/if}
<tr>
<th><strong>{$form.f.content.labelTag}:</strong></th>
<td>{if $form.f.content.errors}{$form.f.content.fieldErrors}{/if}
{$form.f.content|unsafe}
</td>
</tr>
<tr>
<th><strong>{$form.f.comment.labelTag}:</strong></th>
<td>{if $form.f.comment.errors}{$form.f.comment.fieldErrors}{/if}
{$form.f.comment|unsafe}<br />
<span class="helptext">{$form.f.comment.help_text}</span>
</td>
</tr>{if $isOwner or $isMember}
<tr>
<th>{$form.f.label1.labelTag}:</th>
<td>
{if $form.f.label1.errors}{$form.f.label1.fieldErrors}{/if}{$form.f.label1|unsafe}
{if $form.f.label2.errors}{$form.f.label2.fieldErrors}{/if}{$form.f.label2|unsafe}
{if $form.f.label3.errors}{$form.f.label3.fieldErrors}{/if}{$form.f.label3|unsafe}
</td>
</tr>{/if}
<tr>
<td>&nbsp;</td>
<td><input type="submit" value="{trans 'Preview'}" name="preview" /> &nbsp; <input type="submit" value="{trans 'Update Page'}" name="submit" /> | <a href="{url 'IDF_Views_Wiki::index', array($project.shortname)}">{trans 'Cancel'}</a>
</td>
</tr>
</table>
</form>
{/block}
{block context}
<div class="issue-submit-info">
{include 'idf/wiki/edit-info.html'}
</div>
{/block}
{block javascript}
{include 'idf/wiki/js-autocomplete.html'}{/block}

View File

@ -0,0 +1,22 @@
{extends "idf/wiki/base.html"}
{block docclass}yui-t3{assign $inView=true}{/block}
{block body}
<p class="desc">{$page.summary}</p>
{markdown $rev.content, $request}
{/block}
{block context}
{assign $submitter = $page.get_submitter()}
<p><strong>{trans 'Created:'}</strong> <span class="nobrk">{$page.creation_dtime|dateago}</span><br /><span class="nobrk">{blocktrans}by {$submitter}{/blocktrans}</span></p>
{if $rev.creation_dtime != $page.creation_dtime}<p>{assign $submitter = $rev.get_submitter()}
<strong>{trans 'Updated:'}</strong> <span class="nobrk">{$rev.creation_dtime|dateago}</span><br /><span class="nobrk">{blocktrans}by {$submitter}{/blocktrans}</span></p>{/if}
{if $tags.count()}
<p>
<strong>{trans 'Labels:'}</strong><br />
{foreach $tags as $tag}
<span class="label"><strong>{$tag.class}:</strong>{$tag.name}</span><br />
{/foreach}
</p>{/if}
{/block}

View File

@ -578,3 +578,14 @@ div.download-file {
-moz-border-radius: 5px; -moz-border-radius: 5px;
-webkit-border-radius: 5px; -webkit-border-radius: 5px;
} }
/**
* Wiki
*/
p.desc {
background-color: #eeeeec;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
padding: 4px;
width: 60%;
}