diff --git a/src/IDF/Form/TabsConf.php b/src/IDF/Form/TabsConf.php
index a8e6613..c399b9d 100644
--- a/src/IDF/Form/TabsConf.php
+++ b/src/IDF/Form/TabsConf.php
@@ -36,6 +36,7 @@ class IDF_Form_TabsConf extends Pluf_Form
$this->project = $extra['project'];
$ak = array('downloads_access_rights' => __('Downloads'),
+ 'wiki_access_rights' => __('Documentation'),
'source_access_rights' => __('Source'),
'issues_access_rights' => __('Issues'),);
foreach ($ak as $key=>$label) {
diff --git a/src/IDF/Form/WikiConf.php b/src/IDF/Form/WikiConf.php
new file mode 100644
index 0000000..5ed321a
--- /dev/null
+++ b/src/IDF/Form/WikiConf.php
@@ -0,0 +1,66 @@
+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),
+ ));
+
+ }
+}
+
+
diff --git a/src/IDF/Form/WikiCreate.php b/src/IDF/Form/WikiCreate.php
new file mode 100644
index 0000000..e7c90a1
--- /dev/null
+++ b/src/IDF/Form/WikiCreate.php
@@ -0,0 +1,203 @@
+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;
+ }
+}
diff --git a/src/IDF/Form/WikiUpdate.php b/src/IDF/Form/WikiUpdate.php
new file mode 100644
index 0000000..12214ce
--- /dev/null
+++ b/src/IDF/Form/WikiUpdate.php
@@ -0,0 +1,241 @@
+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;
+ }
+}
diff --git a/src/IDF/Middleware.php b/src/IDF/Middleware.php
index e6fdfe0..798a068 100644
--- a/src/IDF/Middleware.php
+++ b/src/IDF/Middleware.php
@@ -53,6 +53,7 @@ class IDF_Middleware
$request->conf = new IDF_Conf();
$request->conf->setProject($request->project);
$ak = array('downloads_access_rights' => 'hasDownloadsAccess',
+ 'wiki_access_rights' => 'hasWikiAccess',
'source_access_rights' => 'hasSourceAccess',
'issues_access_rights' => 'hasIssuesAccess');
$request->rights = array();
diff --git a/src/IDF/Migrations/7Wiki.php b/src/IDF/Migrations/7Wiki.php
new file mode 100644
index 0000000..dd4ced9
--- /dev/null
+++ b/src/IDF/Migrations/7Wiki.php
@@ -0,0 +1,54 @@
+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();
+ }
+}
\ No newline at end of file
diff --git a/src/IDF/Migrations/Install.php b/src/IDF/Migrations/Install.php
index 452129b..4bfed6c 100644
--- a/src/IDF/Migrations/Install.php
+++ b/src/IDF/Migrations/Install.php
@@ -40,6 +40,8 @@ function IDF_Migrations_Install_setup($params=null)
'IDF_IssueFile',
'IDF_Commit',
'IDF_Timeline',
+ 'IDF_WikiPage',
+ 'IDF_WikiRevision',
);
$db = Pluf::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');
if ($perm) $perm->delete();
$models = array(
+ 'IDF_WikiRevision',
+ 'IDF_WikiPage',
'IDF_Timeline',
'IDF_IssueFile',
'IDF_Search_Occ',
diff --git a/src/IDF/Precondition.php b/src/IDF/Precondition.php
index c26c8f5..2738e27 100644
--- a/src/IDF/Precondition.php
+++ b/src/IDF/Precondition.php
@@ -143,6 +143,15 @@ class IDF_Precondition
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.
*
diff --git a/src/IDF/Project.php b/src/IDF/Project.php
index 9f76b1b..9f1194a 100644
--- a/src/IDF/Project.php
+++ b/src/IDF/Project.php
@@ -283,10 +283,9 @@ class IDF_Project extends Pluf_Model
/**
* Generate the tag clouds.
*
- * Return an array of tags sorted class, then name. Each tag get
- * the extra property 'nb_use' for the number of use in the
- * project. For issues, only open issues are used to generate the
- * cloud.
+ * Return an array of tags sorted by class, then name. Each tag
+ * get the extra property 'nb_use' for the number of use in the
+ * project.
*
* @param string ('issues') 'closed_issues' or 'downloads'
* @return ArrayObject of IDF_Tag
diff --git a/src/IDF/Template/IssueComment.php b/src/IDF/Template/IssueComment.php
index dd52cff..6c2c85e 100644
--- a/src/IDF/Template/IssueComment.php
+++ b/src/IDF/Template/IssueComment.php
@@ -32,13 +32,13 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
private $request = 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->request = $request;
$this->scm = IDF_Scm::get($request);
if ($wordwrap) $text = wordwrap($text, 69, "\n", true);
- $text = Pluf_esc($text);
+ if ($esc) $text = Pluf_esc($text);
$text = ereg_replace('[[:alpha:]]+://[^<>[:space:]]+[[:alnum:]/]',
'\\0',
$text);
diff --git a/src/IDF/Template/Markdown.php b/src/IDF/Template/Markdown.php
index b3d37f3..6c0cc7e 100644
--- a/src/IDF/Template/Markdown.php
+++ b/src/IDF/Template/Markdown.php
@@ -23,80 +23,55 @@
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();
- return Pluf_Template::markSafe(Pluf_Text_MarkDown_parse($filter->go($mdtext)));
+ private $project = null;
+ 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('<', '>'),
+ 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)
+ {
+ $sql = new Pluf_SQL('project=%s AND title=%s',
+ array($this->project->id, $m[1]));
+ $pages = Pluf::factory('IDF_WikiPage')->getList(array('filter'=>$sql->gen()));
+ if ($pages->count() != 1) {
+ return $m[0];
+ }
+ if (!$this->request->rights['hasWikiAccess']) {
+ return $m[1];
+ }
+ return ''.$m[1].'';
+ }
}
-/**
- * Strict class to only allow entities.
- */
-class IDF_Template_Markdown 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',
- );
-}
diff --git a/src/IDF/Template/MarkdownPrefilter.php b/src/IDF/Template/MarkdownPrefilter.php
new file mode 100644
index 0000000..5e56f82
--- /dev/null
+++ b/src/IDF/Template/MarkdownPrefilter.php
@@ -0,0 +1,94 @@
+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.
*/
@@ -292,7 +339,8 @@ class IDF_Views_Project
} else {
$params = array();
$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) {
$_val = $request->conf->getVal($key, false);
if ($_val !== false) {
diff --git a/src/IDF/Views/Wiki.php b/src/IDF/Views/Wiki.php
new file mode 100644
index 0000000..91ef650
--- /dev/null
+++ b/src/IDF/Views/Wiki.php
@@ -0,0 +1,279 @@
+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 %s 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 %s 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 = '
'.implode(', ', $tags).'';
+ }
+ return Pluf_esc($page->summary).$out;
+}
diff --git a/src/IDF/WikiPage.php b/src/IDF/WikiPage.php
new file mode 100644
index 0000000..3cc7c34
--- /dev/null
+++ b/src/IDF/WikiPage.php
@@ -0,0 +1,192 @@
+_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 = '
Instructions:
+List one status value per line in desired sort-order.
+Optionally, use an equals-sign to document the meaning of each status value.
+{/blocktrans} +Instructions:
+The content of the page can use the Markdown syntax.
+Website addresses are automatically linked and you can link to another page in the documentation using double square brackets like that [[AnotherPage]].
+{/blocktrans} diff --git a/src/IDF/templates/idf/wiki/index.html b/src/IDF/templates/idf/wiki/index.html new file mode 100644 index 0000000..b6f5212 --- /dev/null +++ b/src/IDF/templates/idf/wiki/index.html @@ -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)} +{/if} + +{/block} +{block context} +{trans 'Number of pages:'} {$pages.nb_items}
+{/block} + diff --git a/src/IDF/templates/idf/wiki/js-autocomplete.html b/src/IDF/templates/idf/wiki/js-autocomplete.html new file mode 100644 index 0000000..0d00d55 --- /dev/null +++ b/src/IDF/templates/idf/wiki/js-autocomplete.html @@ -0,0 +1,27 @@ +{if $isOwner or $isMember} + + + +{/if} diff --git a/src/IDF/templates/idf/wiki/update.html b/src/IDF/templates/idf/wiki/update.html new file mode 100644 index 0000000..3b44194 --- /dev/null +++ b/src/IDF/templates/idf/wiki/update.html @@ -0,0 +1,72 @@ +{extends "idf/wiki/base.html"} +{block docclass}yui-t2{/block} +{block body} + +{if $preview} +{$page.summary}
+ +{markdown $rev.content, $request} + +{/block} +{block context} +{assign $submitter = $page.get_submitter()} +{trans 'Created:'} {$page.creation_dtime|dateago}
{blocktrans}by {$submitter}{/blocktrans}
{assign $submitter = $rev.get_submitter()}
+{trans 'Updated:'} {$rev.creation_dtime|dateago}
{blocktrans}by {$submitter}{/blocktrans}
+{trans 'Labels:'}
+{foreach $tags as $tag}
+{$tag.class}:{$tag.name}
+{/foreach}
+