As promised at the end of last week, I've prepared a list of what has to be done to replicate the sitedoc system for internal documentation. I've also found a few possible security issues, discussed below
Since they are spread out among several different nodes and I'd like feedback on them as a group, I'm listing them here instead of submitting several disconnected patches. A number of these changes have the added benefit of making the site doc infrastructure easier to reuse should we find a need for yet another discrete documentation collection. The document collection interface can be grouped into several subsystems, each of which has its own set of changes: (links lead inside the readmore tags.
I've tried to check the code below for typos, but I haven't run it (or what I can of it) through a perl compiler so I expect there are many I have missed. If you have suggestions or recommendations for the best way to check code snippets before I submit patches, please let me know. And, of course, if you see something that needs to be fixed, please either message me or add a note below.
A lot of information about the nodetypes involved in site documentation is repetitively hardcoded throughout htmlpages, accessrules, opcodes, and htmlcodes. The list below documents all the places where this happens and tries to suggest solutions that reduce or at least contain the amount of hard coding. Ideally though, this kind of data should be moved out of the code nodes and stored in database fields or settings where it can be stated once and all can access it.
Moving the data out would allow us to treat each of these collections (tutorials, QandA, site documentation, pmdev documentation) as instances of a single type "docball". With such a common type it would be easier to patch and/or add features to all such collections at once. It would also make it easier to create new document collections. With three existing document collections (sitedoc, catqa, tut) and a new one proposed (pmdv), clearly a docball is not a singleton object. Ideally the decision as to whether or not we create new collections should be driven by the benefits of having a separate collection and not by concerns about the time it takes to find (or correctly patch) all the places that need to be changed.
Best, beth
The sitedoc infrastructure inherits most of its htmlpages from node,document,doclist, and docstring. There are only three sitedoc specific htmlpages (sitefaqlet display page, sitedoclet display page, and sitefaqlet edit page). Thankfully docstring and doclist have no hard coding and only minor changes need to be made to the faq/doc system:
This node records in one place all the information currently scattered about for documentation sets. I've only used it in code for new nodes and existing nodes that could use it without restructuring. Some nodes have hard coded data so scattered that it wasn't possible to use. We could go back and revise those nodes to use it as well at some point. Placing this data in code, even in a single code location isn't ideal. It would be rather easy to mess up the data. Ideally, this should be in the database or in settings where it can be edited more easily per-docball.
# !!!HARDCODED DATA!!! - common data with [rebless] # Note: for ease of use hash key names are the same as # the section id used by Update Master Faqlist, if # one exists. (Note: pmdv chosen rather than pmd to # avoid confusion with [PMD] (Perl Monks Discussion) # # faq SiteDocClan documentation # tut Tutorials # pmdv pmdev documentation # cqa Categorized QandA # # array elements in $hNodetypes values: # [0] = type label for nodelet links # [1] = id of master list for nodetype # [2] = 1 if rendered using get_sitedoclet # 0 if not my $hAllDocballs = { faq => { masterlist => 481919 , altview => "SDC View" , RJEfilter => 'SDC' , usergroup => 'SiteDocClan' , listtype => 'faqlist' , stringtype => 'faqstring' , reblessable => [qw(sitedoclet sitefaqlet alphafaqlet)] , nodetypes => { sitefaqlet => ['faq',1, 48286] , sitedoclet => ['doc',1, 482316, 1] , alphafaqlet => ['alpha',1,481721] , faqlist => ['faqlist',0,481919] , faqstring => ['faqstring'] } } , tut => { masterlist => 743094 , altview => 'Pedagogue View' , RJEfilter => 'Ped' , usergroup => 'Pedagogues' , listtype => 'tutlist' , stringtype => 'tutstring' , reblessable => [] , nodetypes => { tutlist => ['list', 743094] , tutstring => ['string', 743096] , perltutorial => ['tut', 743095] } } , cqa => { masterlist => undef , altview => 'QandAEditors View' , usergroup => 'QandAEditors' , listtype => 'catqalist' , stringtype => 'catqastring' , reblessable => [] , nodetypes => { catqalist => ['list', undef] , catqastring => ['string', undef] , 'categorized question' => ['q',undef] , 'categorized answer' => ['a',undef] } } , pmdv => { masterlist => ???? , altview => "PDM View" , usergroup => 'pmdev' , listtype => 'pmdvlist' , stringtype => 'pmdvstring' , reblessable => [qw(pmdvfaqlet,pmdvdoclet)] , nodetypes => { pmdvfaqlet => ['faq',1, ????] , pmdvdoclet => ['doc',1, ????, 1] , pmdvlist => ['list', ????] , pmdvstring => ['string', ????] } } }; my $hBogus = { masterlist => undef , altview => "Abnormal View" , usergroup => undef , listtype => undef , stringtype => undef , reblessable => undef # empty nodetype hash flags this as bogus , nodetypes => {} } }; # If a user group is the assigned editor for more # than one docball, then we need to find the primary # one when $id_type = 'usergroup'. This hash keeps # track of each group's primary docball. my $hUsergroupDocballs = { SiteDocClan => 'faq' , pmdev => 'pmdv' , Pedagogues => 'tut' , QandAEditors => 'cqa' }; # END (hardcoded data) # NOTE: if docballs were set up as a nodetype, then # $keytype 'node' should also be added # # $docball_id id of docball - depends on $keytype # $id_type type of identifier # may be one of the following strings: # list - id is list nodetype # section - id is master list section # usergroup - id is user group. # primary docball for user group will # be returned # all - hash keyed by section # containing all docballs # bogus - hash for bogus docball. # (scalar keys $hBogus->{nodetypes} # is always 0) # defaults to section my ($docball_id, $id_type) = @_; $id_type = 'section' unless defined($id_type); # Returning references SHOULD work. # Even though the Everything Bible says that htmlcode # nodes must return a string, in reality # Everything::HTML::htmlcode returns in scalar context # whatever eval returns (unless there is a warning or # exception in which case it stringifies the return # result and appends the error message). if ($id_type eq 'all') { return $hAllDocballs; } elsif ($id_type eq 'section') { return $hAllDocballs->{$docball_id} if exists($hAllDocballs->{$docball_id}); } elsif ($id_type eq 'list') { # assume that each docball has its own list type # and so uniquely identifies a docball # otherwise why would we need a separate docball? foreach $k (keys %$hAllDocballs) { $v = $hAllDocballs->{$k}; return $v if ($v->{listtype} eq $docball_id); } } elsif ($id_type eq 'usergroup') { my $k = $hUsergroupDocballs->{$id_type}; return $hAllDocballs->{$docball_id} if (defined($k) && exists($hAllDocballs->{$k})); } return $hBogus; #not a docball
# replace lines 3-8 my $sType = $NODE->{type}{title}; my $altlabel = $altview ? "Normal View" : htmlcode('get_docball_data','',$sType)->{altview});
#lines 131-139 # these hardcode the string 'sitedoc' but for now # no change is recommended. SiteDocClan documents # have a special feature whereby any node X can have # an accompanying document named "X sitedoc". This # feature is not needed at present for the pmdv docball # - see below "Annotating other documents" for further # discussion. #replace lines 149-151 if (my $sdl=getNode ("$node->{title} sitedoclet",'sitedoclet') push @out, htmlcode('get_sitedoclet','',$sdl); } elsif ($NODE->{type}{title} eq 'pmdvdoclet') { push @out, htmlcode('get_sitedoclet','',$NODE); }
# replace lines 15-20 my $hDocball = htmlcode('get_docball_data','' , $NODE->{type}{title}, 'list'); htmlcode('groupeditor', 'string' , $hDocball->{stringtype});
[% # make very sure we pass a hash to avoid # defaulting to a sitedoclet. Passing anything # other than a hash will trigger the defaulting # mechanism of [get_sitedoclet] $DB->getRef($NODE);$ htmlcode('get_sitedoclet', $NODE); %]
[% # derive updaters group from node # Note: this code is also used in [doclist_edit_page] # and [docstring_edit_page] my $updaters_group = getNodeById($NODE->{type}{updaters_user})-> +{title}; htmlcode('handle_node_edits', '', $updaters_group); %]
This rule is needed by handle_node_edits because handle_node_edits calls access rules directly without parameters rather than passing them through isApproved()!
Note: this does not use get_docball_data because it is dependent on user group and not docball.
# !!!HARDCODED DATA!!! # !!!must be synchronized with group-based rules stored # !!!in the database!!! my @aNodetypes = qw(pmdvfaqlet pmdvdoclet pmdvlist pmdvstring pmdevnote); # END (hard coded data) my $node = shift; $node ||=$NODE; $DB->getRef($node); #convert to hash if need be my $sType = $node->{type}{title}; return grep {$sType eq $_} @aNodetypes;
# insert into hash @ lines 16-39: pmdev => { is_ok_type => 'CanPmdevEdit', not_ok_type_msg => 'Not a Pmdev editable type', not_member_msg => 'You're not a pmdevil get away, there is no Perl Illuminati' } # replace line 143 with: } elsif ( $NODE->{type}{title} =~ /^(?:sitedoclet|pmdvdoclet)$/ ) {
Each document collection (tutorials, site documentation, categorized questions and answers) has its own set of master document lists generated by calling the Update Master Faqlist superdoc with an appropriately set "section" parameter. Current values for this parameter are:
To extend this infrastructure to include pmdev documentation we need to make the following changes:
# replace lines 5-13 (inside for loop) # not quite sure why this is inside a for loop since # only one value of $usergroup, etc can be set. my $section = lc($_); my $hDocball = htmlcode('get_docball_data','',$section); #bogus docball has no nodetypes if (scalar keys $hDocball->{nodetypes}) { $usergroup = $hDocball->{usrgroup}; $list_type = $hDocball->{listtype}; $other_types = grep { $_ ne $list_type } keys %{$hDocball->{nodetypes}}; # there is no point in continuing further: # code currently supports only one section even # though the section is being tested within a for loop last; }
CRUD operations are triggered by sending a request for the appropriate URL.
The following changes need to be made so that CRUD operations will behave properly for the pmdev document collection:
#replace line 5 #remove hardcoded value for author_user $$N{author_user} = getNodeById($node_id->{type}{writers_user})->{ti +tle};
my $node = $q->param('node_id'); my $sNewType = $q->param('rebless_to'); my $user = $USER; $node =|| $NODE; my $hType = $node->{type}; my $sOldType = $hType->{title}; if ($sOldType ne $sNewType) { my $group = getNodeById($hType->{updaters_user})->{title}; if ($DB->isApproved($user, $group)) { my $aReblessable = htmlcode('get_docball_data', '' , $group, 'usergroup')->{reblessable}; my ($bCanReblessFrom, $bCanReblessTo); foreach (@$aReblessable) { if ($_ eq $sOldType) { $bCanReblessFrom = 1; } elsif ($_ eq $sNewType) { $bCanReblessTo = 1; } } if ($bCanReblessFrom && $bCanReblessTo) { Everything::printLog('Rebless '. $user->{title} . ' #'. $node->{node_id} . ' - '. $node->{title} . " from $sOldType -> $sNewType"); $hType = getType($sNewType); $node->{type_nodetype}=$type->{node_id}; updateNode($node, $user); } } }
Both Recently Active Threads and Newest Nodes use Newest Nodes Settings to decide which nodes to include. To make changes to the pmdv docball visible to developers we need to add a setting key for each pmdv docball nodetype and update the settings storing nodes that are visible:
To see recent updates (rather than new nodes), one needs to look at the Recent Janitorial Edits page. This page needs to be modified as follows:
# replace lines 19-21 my $hAllDocballs = htmlcode('get_docball_data','',undef,'all'); # replace lines 24-27 my $bFiltered = $q->param('Wi') && $type eq 'wiki'; if (!$bFiltered) { my $type = getNodeById($edit->{edithistory_id} )->{type}{title}; my $sDocball; foreach my $k (keys %$hAllDocballs) { # if $type is one of the nodetypes for this docball # then test the RJE filter param for this nodeball if ($hAllNodeballs->{$k}->{nodetypes}{$type}) { my $sParam = $hAllNodeballs->{$k}{RJEfilter}; $sParam = $k unless defiend($sParam); if ($q->param($sParam)) { $bFiltered = 1; last; } } } } if ($bFiltered) {
Edit history is created when docball nodetypes are saved via doclist edit page or sitefaqlet edit page, both of which call handle_node_edits to save changes. History is displayed either via the Recent Janitorial Edits or by a URL where "displaytype=edithistory". This triggers the standard edit history page for nodes: node edithistory page.
The necessary changes to support the use of these pages for other docballs has already been discussed above in the following sections:
The site documentation system can use the htmlcode node showsitedoclet to embed a site document into another page. This feature is sometimes used to provide humorous commentary about user groups and access rules.
At some pont we may decide that it would be helpful to annotate design elements like dbtables, nodetypes, and htmlpages with some commentary about their role in the larger system. However, first we need to have some documentation suitable for that purpose.
Our present primary concern is making it easy to create and keep track of documentation. This feature applies more to how documentation is used so it isn't essential to short term goals. Thus there are no immediate plans to modify showsitedoclet to work with anything but sitedoclets.
The SiteDocClan provides a number of different ways for users to work with documentation:
The organization of the pmdv docball user interface will of course evolve over time. Initially, we can just place the following links at the bottom of the PmDev Nodelet using the following code changes. (Note: changes have been designed with reuse in mind. Hardcoded data that should be moved out to settings or database fields has been consolidated and marked.
#replace line 73: ); with htmlcode('genDocballEditorLinks,'', $NODE, $USER, 'pmdev'));
my ($node, $user, $group) = @_; return '' unless $DB->isApproved($user,$group); my ($history,$rebless,$masterlist,$create) = ''x3; my $hType = $node->{type}; my $sType = $hType->{title}; my $hDocball = htmlcode('get_docball_data','' , $group, 'usergroup'); #generate history links $history= linkNode(getId($node), "Node history" , {displaytype => 'edithistory'} ); #generate reblessing links my $aReblessable = $hDocball->{reblessable}; if (grep { $_ eq $sType } @$aReblessable) { my @links; foreach my $key (sort @$aReblessable) { next if $key eq $hType->{title}; my $label = $hNodetypes->{$key}[0]; my $link = linkNode($node, $label { op=>'rebless', rebless_to => $key }); push @links, $link; } $rebless= join " ","Rebless as ",@links; } #getnerate master document list link $history= linkNode(getId($hDocball->{masterlist}) , "Master Lists" , {displaytype => 'display'} ); #generate create links { my @links; foreach my $key (keys %$hNodetypes) { my $aData = $hNodetypes->{$key}; my $label = $aData->[0]; my $hLinkParams = { op=>'new', type => $key }; my $iGroupNode = $aData->[2]; if ($iGroupNode) { $hLinkParams->{addToGroup} = $iGroupNode; } my $link = linkNode($node, $label, $hLinkParams); push @links, $link; } $create= join " ","Create: ",@links; } return join ' | ', grep length($_), $history, $rebless, $masterlist, $create);
Here are some things we may want to consider after we implemented the above:
As I was reading through the code I noticed a few places where the code bypasses the permissions defined for each nodetype in the database:
In reply to Re: Pmdev documentation
by ELISHEVA
in thread Pmdev documentation
by ELISHEVA
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |