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) -{ - $filter = new IDF_Template_Markdown(); - return Pluf_Template::markSafe(Pluf_Text_MarkDown_parse($filter->go($mdtext))); -} - /** - * Strict class to only allow entities. + * Make the links to issues and commits. */ -class IDF_Template_Markdown extends Pluf_Text_HTML_Filter +class IDF_Template_Markdown extends Pluf_Template_Tag { - 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', - ); + 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].''; + } } + 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. */ public $adminMembers_precond = array('IDF_Precondition::projectOwner'); @@ -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 = ''. + Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')). + ''; + $submitter = $this->get_submitter(); + $out .= sprintf(__('%2$s, %3$s'), $url, Pluf_esc($this->title), Pluf_esc($this->summary)).''; + $out .= "\n".' +
'.sprintf(__('Creation of page %s'), $url, Pluf_esc($this->title)).', '.__('by').' '.Pluf_esc($submitter).'
'; + return Pluf_Template::markSafe($out); + } +} \ No newline at end of file diff --git a/src/IDF/WikiRevision.php b/src/IDF/WikiRevision.php new file mode 100644 index 0000000..48e0794 --- /dev/null +++ b/src/IDF/WikiRevision.php @@ -0,0 +1,183 @@ +_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".''. + Pluf_esc(Pluf_Template_dateAgo($this->creation_dtime, 'without')). + ''; + $submitter = $this->get_submitter(); + $out .= sprintf(__('%2$s, %3$s'), $url, Pluf_esc($page->title), Pluf_esc($this->summary)); + //$out .= ''.__('Summary:').' '.Pluf_esc($this->summary).' '; + if ($this->changedRevision()) { + $out .= '
'; + foreach ($this->changes as $w => $v) { + $out .= ''; + switch ($w) { + case 'lb': + $out .= __('Labels:'); break; + } + $out .= ' '; + if ($w == 'lb') { + $out .= Pluf_esc(implode(', ', $v)); + } else { + $out .= Pluf_esc($v); + } + $out .= ' '; + } + $out .= '
'; + } + $out .= ''; + $out .= "\n".' +
'.sprintf(__('Change of %s'), $url, Pluf_esc($page->title)).', '.__('by').' '.Pluf_esc($submitter).'
'; + return Pluf_Template::markSafe($out); + } +} diff --git a/src/IDF/conf/idf.php-dist b/src/IDF/conf/idf.php-dist index 79c7857..9a1c7d0 100644 --- a/src/IDF/conf/idf.php-dist +++ b/src/IDF/conf/idf.php-dist @@ -161,10 +161,10 @@ $cfg['template_tags'] = array( 'hotkey' => 'IDF_Template_HotKey', 'issuetext' => 'IDF_Template_IssueComment', 'timeline' => 'IDF_Template_TimelineFragment', + 'markdown' => 'IDF_Template_Markdown', ); $cfg['template_modifiers'] = array( 'size' => 'IDF_Views_Source_PrettySize', - 'markdown' => 'IDF_Template_Markdown_filter', ); // available languages diff --git a/src/IDF/conf/urls.php b/src/IDF/conf/urls.php index 3eedef9..f59584d 100644 --- a/src/IDF/conf/urls.php +++ b/src/IDF/conf/urls.php @@ -147,37 +147,37 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/attachment/(\d+)/(.*)$#', // ---------- SCM ---------------------------------------- -$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/(\w+)/$#', +$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/([\S^/]+)/$#', 'base' => $base, 'priority' => 4, 'model' => 'IDF_Views_Source', 'method' => 'treeBase'); -$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/(\w+)/(.*)$#', +$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/tree/([\S^/]+)/(.*)$#', 'base' => $base, 'priority' => 4, 'model' => 'IDF_Views_Source', 'method' => 'tree'); -$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changes/(\w+)/$#', +$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changes/([\S^/]+)/$#', 'base' => $base, 'priority' => 4, 'model' => 'IDF_Views_Source', 'method' => 'changeLog'); -$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/commit/(\w+)/$#', +$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/commit/([\S^/]+)/$#', 'base' => $base, 'priority' => 4, 'model' => 'IDF_Views_Source', 'method' => 'commit'); -$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/download/(\w+)/$#', +$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/download/([\S^/]+)/$#', 'base' => $base, 'priority' => 4, 'model' => 'IDF_Views_Source', 'method' => 'download'); -$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/file/(\w+)/(.*)$#', +$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/file/([\S^/]+)/(.*)$#', 'base' => $base, 'priority' => 4, 'model' => 'IDF_Views_Source', @@ -195,6 +195,32 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changesrev/$#', 'model' => 'IDF_Views_Source_Svn', '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 ------------------------------------ $ctl[] = array('regex' => '#^/p/([\-\w]+)/downloads/$#', @@ -254,6 +280,12 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/admin/downloads/$#', 'model' => 'IDF_Views_Project', '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/$#', 'base' => $base, 'priority' => 4, diff --git a/src/IDF/relations.php b/src/IDF/relations.php index 3e12dfe..a007306 100644 --- a/src/IDF/relations.php +++ b/src/IDF/relations.php @@ -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'), 'relate_to_many' => array('IDF_Tag')); $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; diff --git a/src/IDF/templates/idf/admin/base.html b/src/IDF/templates/idf/admin/base.html index 2354605..0b4b761 100644 --- a/src/IDF/templates/idf/admin/base.html +++ b/src/IDF/templates/idf/admin/base.html @@ -3,8 +3,9 @@ {block subtabs}
{trans 'Project Summary'} | -{trans 'Issue Tracking'} | {trans 'Downloads'} | +{trans 'Documentation'} | +{trans 'Issue Tracking'} | {trans 'Source'} | {trans 'Project Members'} | {trans 'Tabs Access'} diff --git a/src/IDF/templates/idf/admin/tabs.html b/src/IDF/templates/idf/admin/tabs.html index 8c42c33..40dfe30 100644 --- a/src/IDF/templates/idf/admin/tabs.html +++ b/src/IDF/templates/idf/admin/tabs.html @@ -18,6 +18,12 @@ +{$form.f.wiki_access_rights.labelTag}: +{if $form.f.wiki_access_rights.errors}{$form.f.wiki_access_rights.fieldErrors}{/if} +{$form.f.wiki_access_rights|unsafe} + + + {$form.f.issues_access_rights.labelTag}: {if $form.f.issues_access_rights.errors}{$form.f.issues_access_rights.fieldErrors}{/if} {$form.f.issues_access_rights|unsafe} diff --git a/src/IDF/templates/idf/admin/wiki.html b/src/IDF/templates/idf/admin/wiki.html new file mode 100644 index 0000000..cd28aca --- /dev/null +++ b/src/IDF/templates/idf/admin/wiki.html @@ -0,0 +1,34 @@ +{extends "idf/admin/base.html"} +{block docclass}yui-t1{assign $inWiki = true}{/block} +{block body} +
+ + + + + + + + + + +
{$form.f.labels_wiki_predefined.labelTag}:
+{if $form.f.labels_wiki_predefined.errors}{$form.f.labels_wiki_predefined.fieldErrors}{/if} +{$form.f.labels_wiki_predefined|unsafe} +
{$form.f.labels_wiki_one_max.labelTag}:
+{if $form.f.labels_wiki_one_max.errors}{$form.f.labels_wiki_one_max.fieldErrors}{/if} +{$form.f.labels_wiki_one_max|unsafe} +
+ +
+
+{/block} +{block context} +
+{blocktrans} +

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} +
+{/block} diff --git a/src/IDF/templates/idf/base.html b/src/IDF/templates/idf/base.html index 4ff00d1..f18d168 100644 --- a/src/IDF/templates/idf/base.html +++ b/src/IDF/templates/idf/base.html @@ -43,8 +43,9 @@
{if $project} {trans 'Project Home'} -{if $hasIssuesAccess} {trans 'Issues'}{/if} {if $hasDownloadsAccess} {trans 'Downloads'}{/if} +{if $hasWikiAccess} {trans 'Documentation'}{/if} +{if $hasIssuesAccess} {trans 'Issues'}{/if} {if $hasSourceAccess} {trans 'Source'}{/if} {if $isOwner} {trans 'Administer'}{/if}{/if} diff --git a/src/IDF/templates/idf/project/home.html b/src/IDF/templates/idf/project/home.html index c7601ab..f6fad9b 100644 --- a/src/IDF/templates/idf/project/home.html +++ b/src/IDF/templates/idf/project/home.html @@ -7,7 +7,7 @@
{/block} {block body} -{$project.description|markdown} +{markdown $project.description, $request} {/block} {block context} {if count($downloads) > 0} diff --git a/src/IDF/templates/idf/wiki/base.html b/src/IDF/templates/idf/wiki/base.html new file mode 100644 index 0000000..0219876 --- /dev/null +++ b/src/IDF/templates/idf/wiki/base.html @@ -0,0 +1,10 @@ +{extends "idf/base.html"} +{block tabwiki} class="active"{/block} +{block subtabs} +
+{trans 'List Pages'} +{if !$user.isAnonymous()} | {trans 'New Page'} {/if} +{if !$user.isAnonymous() and $inView} | {trans 'Update This Page'} {/if} +{superblock} +
+{/block} diff --git a/src/IDF/templates/idf/wiki/create.html b/src/IDF/templates/idf/wiki/create.html new file mode 100644 index 0000000..2780222 --- /dev/null +++ b/src/IDF/templates/idf/wiki/create.html @@ -0,0 +1,68 @@ +{extends "idf/wiki/base.html"} +{block docclass}yui-t1{assign $inCreate = true}{/block} +{block body} + +{if $preview} +

{trans 'Preview of the Page'}

+ +{markdown $preview, $request} +{/if} + +{if $form.errors} +
+

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

+{if $form.get_top_errors} +{$form.render_top_errors|unsafe} +{/if} +
+{/if} + +
+ + + + + + + + + + + + +{if $isOwner or $isMember} + + + +{/if} + + + + +
{$form.f.title.labelTag}:{if $form.f.title.errors}{$form.f.title.fieldErrors}{/if} +{$form.f.title|unsafe}
+{$form.f.title.help_text} +
{$form.f.summary.labelTag}:{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if} +{$form.f.summary|unsafe}
+{$form.f.summary.help_text} +
{$form.f.content.labelTag}:{if $form.f.content.errors}{$form.f.content.fieldErrors}{/if} +{$form.f.content|unsafe} +
{$form.f.label1.labelTag}: +{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} +
    | {trans 'Cancel'} +
+
+{/block} +{block context} +
+{include 'idf/wiki/edit-info.html'} +
+{/block} +{block javascript} + +{include 'idf/wiki/js-autocomplete.html'}{/block} + diff --git a/src/IDF/templates/idf/wiki/edit-info.html b/src/IDF/templates/idf/wiki/edit-info.html new file mode 100644 index 0000000..0a0ad21 --- /dev/null +++ b/src/IDF/templates/idf/wiki/edit-info.html @@ -0,0 +1,6 @@ +{assign $url = 'http://daringfireball.net/projects/markdown/syntax'} +{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)} +

+ {trans 'New Page'}

{/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} +

{trans 'Preview of the Page'}

+ +{markdown $preview, $request} +{/if} + +{if $form.errors} +
+

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

+{if $form.get_top_errors} +{$form.render_top_errors|unsafe} +{/if} +
+{/if} + +
+{if $isOwner or $isMember} + + + + + + + +{/if} + + + + + + + +{if $isOwner or $isMember} + + + +{/if} + + + + +
{$form.f.title.labelTag}:{if $form.f.title.errors}{$form.f.title.fieldErrors}{/if} +{$form.f.title|unsafe}
+{$form.f.title.help_text} +
{$form.f.summary.labelTag}:{if $form.f.summary.errors}{$form.f.summary.fieldErrors}{/if} +{$form.f.summary|unsafe}
+{$form.f.summary.help_text} +
{$form.f.content.labelTag}:{if $form.f.content.errors}{$form.f.content.fieldErrors}{/if} +{$form.f.content|unsafe} +
{$form.f.comment.labelTag}:{if $form.f.comment.errors}{$form.f.comment.fieldErrors}{/if} +{$form.f.comment|unsafe}
+{$form.f.comment.help_text} +
{$form.f.label1.labelTag}: +{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} +
    | {trans 'Cancel'} +
+
+{/block} +{block context} +
+{include 'idf/wiki/edit-info.html'} +
+{/block} +{block javascript} +{include 'idf/wiki/js-autocomplete.html'}{/block} + diff --git a/src/IDF/templates/idf/wiki/view.html b/src/IDF/templates/idf/wiki/view.html new file mode 100644 index 0000000..60b7e5e --- /dev/null +++ b/src/IDF/templates/idf/wiki/view.html @@ -0,0 +1,22 @@ +{extends "idf/wiki/base.html"} +{block docclass}yui-t3{assign $inView=true}{/block} +{block body} + +

{$page.summary}

+ +{markdown $rev.content, $request} + +{/block} +{block context} +{assign $submitter = $page.get_submitter()} +

{trans 'Created:'} {$page.creation_dtime|dateago}
{blocktrans}by {$submitter}{/blocktrans}

+{if $rev.creation_dtime != $page.creation_dtime}

{assign $submitter = $rev.get_submitter()} +{trans 'Updated:'} {$rev.creation_dtime|dateago}
{blocktrans}by {$submitter}{/blocktrans}

{/if} +{if $tags.count()} +

+{trans 'Labels:'}
+{foreach $tags as $tag} +{$tag.class}:{$tag.name}
+{/foreach} +

{/if} +{/block} diff --git a/www/media/idf/css/style.css b/www/media/idf/css/style.css index c0c5717..c1c3db3 100644 --- a/www/media/idf/css/style.css +++ b/www/media/idf/css/style.css @@ -578,3 +578,14 @@ div.download-file { -moz-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%; +}