← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 18:42:36 2015
Reported on Fri Jul 31 18:48:13 2015

Filename/var/www/foswikidev/core/lib/Foswiki/Meta.pm
StatementsExecuted 31035175 statements in 35.2s
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
3323098117.39s7.39sFoswiki::Meta::::dataDecodeFoswiki::Meta::dataDecode
927699116.20s6.20sFoswiki::Meta::::isValidEmbeddingFoswiki::Meta::isValidEmbedding
875082224.02s4.02sFoswiki::Meta::::putKeyedFoswiki::Meta::putKeyed
2632732512ms76.4sFoswiki::Meta::::loadVersionFoswiki::Meta::loadVersion
2629665331ms77.1sFoswiki::Meta::::loadFoswiki::Meta::load
5239633329ms329msFoswiki::Meta::::putFoswiki::Meta::put
267611913318ms353msFoswiki::Meta::::newFoswiki::Meta::new (recurses: max depth 1, inclusive time 32µs)
61651107183ms183msFoswiki::Meta::::getFoswiki::Meta::get
12264944138ms138msFoswiki::Meta::::getLoadedRevFoswiki::Meta::getLoadedRev
5265422106ms106msFoswiki::Meta::::setLoadStatusFoswiki::Meta::setLoadStatus
3513177104ms138msFoswiki::Meta::::textFoswiki::Meta::text (recurses: max depth 1, inclusive time 136µs)
700844493.2ms93.2msFoswiki::Meta::::latestIsLoadedFoswiki::Meta::latestIsLoaded
44523251470.6ms70.6msFoswiki::Meta::::topicFoswiki::Meta::topic
263271157.6ms57.6msFoswiki::Meta::::countFoswiki::Meta::count
35941281253.8ms53.8msFoswiki::Meta::::webFoswiki::Meta::web
263562146.3ms46.3msFoswiki::Meta::::addDependencyFoswiki::Meta::addDependency
1112.97ms5.92msFoswiki::Meta::::renderFormForDisplayFoswiki::Meta::renderFormForDisplay
252772.79ms14.2msFoswiki::Meta::::haveAccessFoswiki::Meta::haveAccess
222221.59ms17.4msFoswiki::Meta::::getRevisionInfoFoswiki::Meta::getRevisionInfo
124441.11ms23.5msFoswiki::Meta::::getPreferenceFoswiki::Meta::getPreference
111756µs1.08msFoswiki::Meta::::BEGIN@113Foswiki::Meta::BEGIN@113
38462645µs645µsFoswiki::Meta::::isCacheableFoswiki::Meta::isCacheable
111490µs609µsFoswiki::Meta::::BEGIN@116Foswiki::Meta::BEGIN@116
17243421µs421µsFoswiki::Meta::::getPathFoswiki::Meta::getPath
10043408µs137sFoswiki::Meta::::expandMacrosFoswiki::Meta::expandMacros (recurses: max depth 2, inclusive time 62.6ms)
4011391µs108sFoswiki::Meta::::queryFoswiki::Meta::query
4011313µs2.79sFoswiki::Meta::::eachTopicFoswiki::Meta::eachTopic
2911182µs2.01msFoswiki::Meta::::existsInStoreFoswiki::Meta::existsInStore
302292µs92µsFoswiki::Meta::::findFoswiki::Meta::find
31182µs13.0msFoswiki::Meta::::unloadFoswiki::Meta::unload
496667µs67µsFoswiki::Meta::::sessionFoswiki::Meta::session
31166µs86µsFoswiki::Meta::::setRevisionInfoFoswiki::Meta::setRevisionInfo
52252µs50.2msFoswiki::Meta::::renderTMLFoswiki::Meta::renderTML
11123µs116µsFoswiki::Meta::::hasAttachmentFoswiki::Meta::hasAttachment
31120µs13.0msFoswiki::Meta::::finishFoswiki::Meta::finish
21116µs17.9msFoswiki::Meta::::eachWebFoswiki::Meta::eachWeb
22116µs747µsFoswiki::Meta::::getRevisionHistoryFoswiki::Meta::getRevisionHistory
11113µs27µsFoswiki::Meta::::BEGIN@109Foswiki::Meta::BEGIN@109
11111µs32µsFoswiki::Meta::::BEGIN@545Foswiki::Meta::BEGIN@545
11110µs11µsFoswiki::Meta::::getFormNameFoswiki::Meta::getFormName
1119µs31µsFoswiki::Meta::::BEGIN@112Foswiki::Meta::BEGIN@112
1118µs13µsFoswiki::Meta::::BEGIN@110Foswiki::Meta::BEGIN@110
1118µs43µsFoswiki::Meta::::BEGIN@132Foswiki::Meta::BEGIN@132
1117µs110µsFoswiki::Meta::::BEGIN@111Foswiki::Meta::BEGIN@111
1116µs6µsFoswiki::Meta::::registerMETAFoswiki::Meta::registerMETA
1114µs4µsFoswiki::Meta::::BEGIN@114Foswiki::Meta::BEGIN@114
1114µs4µsFoswiki::Meta::::BEGIN@120Foswiki::Meta::BEGIN@120
0000s0sFoswiki::Meta::::__ANON__[:1834]Foswiki::Meta::__ANON__[:1834]
0000s0sFoswiki::Meta::::__ANON__[:1843]Foswiki::Meta::__ANON__[:1843]
0000s0sFoswiki::Meta::::__ANON__[:1886]Foswiki::Meta::__ANON__[:1886]
0000s0sFoswiki::Meta::::__ANON__[:1890]Foswiki::Meta::__ANON__[:1890]
0000s0sFoswiki::Meta::::__ANON__[:2000]Foswiki::Meta::__ANON__[:2000]
0000s0sFoswiki::Meta::::__ANON__[:2003]Foswiki::Meta::__ANON__[:2003]
0000s0sFoswiki::Meta::::__ANON__[:2148]Foswiki::Meta::__ANON__[:2148]
0000s0sFoswiki::Meta::::__ANON__[:2152]Foswiki::Meta::__ANON__[:2152]
0000s0sFoswiki::Meta::::__ANON__[:2303]Foswiki::Meta::__ANON__[:2303]
0000s0sFoswiki::Meta::::__ANON__[:2309]Foswiki::Meta::__ANON__[:2309]
0000s0sFoswiki::Meta::::__ANON__[:2386]Foswiki::Meta::__ANON__[:2386]
0000s0sFoswiki::Meta::::__ANON__[:2390]Foswiki::Meta::__ANON__[:2390]
0000s0sFoswiki::Meta::::__ANON__[:2453]Foswiki::Meta::__ANON__[:2453]
0000s0sFoswiki::Meta::::__ANON__[:2457]Foswiki::Meta::__ANON__[:2457]
0000s0sFoswiki::Meta::::__ANON__[:2994]Foswiki::Meta::__ANON__[:2994]
0000s0sFoswiki::Meta::::__ANON__[:2997]Foswiki::Meta::__ANON__[:2997]
0000s0sFoswiki::Meta::::__ANON__[:3209]Foswiki::Meta::__ANON__[:3209]
0000s0sFoswiki::Meta::::__ANON__[:3215]Foswiki::Meta::__ANON__[:3215]
0000s0sFoswiki::Meta::::__ANON__[:3296]Foswiki::Meta::__ANON__[:3296]
0000s0sFoswiki::Meta::::__ANON__[:3302]Foswiki::Meta::__ANON__[:3302]
0000s0sFoswiki::Meta::::_assertIsAttachmentFoswiki::Meta::_assertIsAttachment
0000s0sFoswiki::Meta::::_assertIsTopicFoswiki::Meta::_assertIsTopic
0000s0sFoswiki::Meta::::_assertIsWebFoswiki::Meta::_assertIsWeb
0000s0sFoswiki::Meta::::_atomicLockFoswiki::Meta::_atomicLock
0000s0sFoswiki::Meta::::_atomicUnlockFoswiki::Meta::_atomicUnlock
0000s0sFoswiki::Meta::::_makeSummaryTextSafeFoswiki::Meta::_makeSummaryTextSafe
0000s0sFoswiki::Meta::::_summariseTextSimpleFoswiki::Meta::_summariseTextSimple
0000s0sFoswiki::Meta::::_summariseTextWithSearchContextFoswiki::Meta::_summariseTextWithSearchContext
0000s0sFoswiki::Meta::::attachFoswiki::Meta::attach
0000s0sFoswiki::Meta::::clearLeaseFoswiki::Meta::clearLease
0000s0sFoswiki::Meta::::copyAttachmentFoswiki::Meta::copyAttachment
0000s0sFoswiki::Meta::::copyFromFoswiki::Meta::copyFrom
0000s0sFoswiki::Meta::::deleteMostRecentRevisionFoswiki::Meta::deleteMostRecentRevision
0000s0sFoswiki::Meta::::eachAttachmentFoswiki::Meta::eachAttachment
0000s0sFoswiki::Meta::::eachChangeFoswiki::Meta::eachChange
0000s0sFoswiki::Meta::::expandNewTopicFoswiki::Meta::expandNewTopic
0000s0sFoswiki::Meta::::fireDependencyFoswiki::Meta::fireDependency
0000s0sFoswiki::Meta::::forEachSelectedValueFoswiki::Meta::forEachSelectedValue
0000s0sFoswiki::Meta::::getAttachmentRevisionInfoFoswiki::Meta::getAttachmentRevisionInfo
0000s0sFoswiki::Meta::::getContainerFoswiki::Meta::getContainer
0000s0sFoswiki::Meta::::getDifferencesFoswiki::Meta::getDifferences
0000s0sFoswiki::Meta::::getEmbeddedStoreFormFoswiki::Meta::getEmbeddedStoreForm
0000s0sFoswiki::Meta::::getLatestRevFoswiki::Meta::getLatestRev
0000s0sFoswiki::Meta::::getLeaseFoswiki::Meta::getLease
0000s0sFoswiki::Meta::::getParentFoswiki::Meta::getParent
0000s0sFoswiki::Meta::::getRev1InfoFoswiki::Meta::getRev1Info
0000s0sFoswiki::Meta::::getRevisionAtTimeFoswiki::Meta::getRevisionAtTime
0000s0sFoswiki::Meta::::isSessionTopicFoswiki::Meta::isSessionTopic
0000s0sFoswiki::Meta::::mergeFoswiki::Meta::merge
0000s0sFoswiki::Meta::::moveFoswiki::Meta::move
0000s0sFoswiki::Meta::::moveAttachmentFoswiki::Meta::moveAttachment
0000s0sFoswiki::Meta::::onTickFoswiki::Meta::onTick
0000s0sFoswiki::Meta::::openAttachmentFoswiki::Meta::openAttachment
0000s0sFoswiki::Meta::::populateNewWebFoswiki::Meta::populateNewWeb
0000s0sFoswiki::Meta::::putAllFoswiki::Meta::putAll
0000s0sFoswiki::Meta::::removeFoswiki::Meta::remove
0000s0sFoswiki::Meta::::removeFromStoreFoswiki::Meta::removeFromStore
0000s0sFoswiki::Meta::::renderFormFieldForDisplayFoswiki::Meta::renderFormFieldForDisplay
0000s0sFoswiki::Meta::::replaceMostRecentRevisionFoswiki::Meta::replaceMostRecentRevision
0000s0sFoswiki::Meta::::saveFoswiki::Meta::save
0000s0sFoswiki::Meta::::saveAsFoswiki::Meta::saveAs
0000s0sFoswiki::Meta::::setEmbeddedStoreFormFoswiki::Meta::setEmbeddedStoreForm
0000s0sFoswiki::Meta::::setLeaseFoswiki::Meta::setLease
0000s0sFoswiki::Meta::::stringifyFoswiki::Meta::stringify
0000s0sFoswiki::Meta::::summariseChangesFoswiki::Meta::summariseChanges
0000s0sFoswiki::Meta::::summariseTextFoswiki::Meta::summariseText
0000s0sFoswiki::Meta::::testAttachmentFoswiki::Meta::testAttachment
0000s0sFoswiki::Meta::::typeFoswiki::Meta::type
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1# See bottom of file for license and copyright information
2
3=begin TML
4
5---+ package Foswiki::Meta
6
7Objects of this class act as handles onto real store objects. An
8object of this class can represent the Foswiki root, a web, or a topic.
9
10Meta objects interact with the store using only the methods of
11Foswiki::Store. The rest of the core should interact only with Meta
12objects; the only exception to this are the *Exists methods that are
13published by the store interface (and facaded by the Foswiki class).
14
15A meta object exists in one of two states; either unloaded, in which case
16it is simply a lightweight handle to a store location, and loaded, in
17which case it acts as a portal onto the actual store content of a specific
18revision of the topic.
19
20An unloaded object is constructed by the =new= constructor on this class,
21passing one to three parameters depending on whether the object represents the
22root, a web, or a topic.
23
24A loaded object may be constructed by calling the =load= constructor, or
25a previously constructed object may be converted to 'loaded' state by
26calling =loadVersion=. Once an object is loaded with a specific revision, it
27cannot be reloaded.
28
29Unloaded objects return undef from =getLoadedRev=, or the loaded revision
30otherwise.
31
32An unloaded object can be populated through calls to =text($text)=, =put=
33and =putKeyed=. Such an object can be saved using =save()= to create a new
34revision of the topic.
35
36To the caller, a meta object carries two types of data. The first
37is the "plain text" of the topic, which is accessible through the =text()=
38method. The object also behaves as a hash of different types of
39meta-data (keyed on the type, such as 'FIELD' and 'FILEATTACHMENT').
40
41Each entry in the hash is an array, where each entry in the array
42contains another hash of the key=value pairs, corresponding to a
43single meta-datum.
44
45If there may be multiple entries of the same top-level type (i.e. for FIELD
46and FILEATTACHMENT) then the array has multiple entries. These types
47are referred to as "keyed" types. The array entries are keyed with the
48attribute 'name' which must be in each entry in the array.
49
50For unkeyed types, the array has only one entry.
51
52Pictorially,
53 * TOPICINFO
54 * author => '...'
55 * date => '...'
56 * ...
57 * FILEATTACHMENT
58 * [0] = { name => 'a' ... }
59 * [1] = { name => 'b' ... }
60 * FIELD
61 * [0] = { name => 'c' ... }
62 * [1] = { name => 'd' ... }
63
64Implementor note: the =_indices= field gives a quick lookup into this
65structure; it is a hash of top-level types, each mapping to a hash indexed
66on the key name. For the above example, it looks like this:
67 * _indices => {
68 FILEATTACHMENT => { a => 0, b => 1 },
69 FIELD => { c => 0, d => 1 }
70 }
71It is maintained on the fly by the methods of this module, which makes it
72important *not* to write new data directly into the structure, but *always*
73to go through the methods exported from here.
74
75As required by the contract with Foswiki::Store, version numbers are required
76to be positive, non-zero integers. When passing in version numbers, 0,
77undef and '' are treated as referring to the *latest* (most recent)
78revision of the object. Version numbers are required to increase (later
79version numbers are greater than earlier) but are *not* required to be
80sequential.
81
82*IMPORTANT* the methods on =Foswiki::Meta= _do not check access permissions_
83(other than =haveAccess=, obviously).
84This is a deliberate design decision, as these checks are expensive and many
85callers don't require them. For this reason, be *very careful* how you use
86=Foswiki::Meta=. Extension authors will almost always find the methods
87they want in =Foswiki::Func=, rather than in this class.
88
89*Since* _date_ indicates where functions or parameters have been added since
90the baseline of the API (Foswiki release 4.2.3). The _date_ indicates the
91earliest date of a Foswiki release that will support that function or
92parameter.
93
94*Deprecated* _date_ indicates where a function or parameters has been
95[[http://en.wikipedia.org/wiki/Deprecation][deprecated]]. Deprecated
96functions will still work, though they should
97_not_ be called in new plugins and should be replaced in older plugins
98as soon as possible. Deprecated parameters are simply ignored in Foswiki
99releases after _date_.
100
101*Until* _date_ indicates where a function or parameter has been removed.
102The _date_ indicates the latest date at which Foswiki releases still supported
103the function or parameter.
104
105=cut
106
107package Foswiki::Meta;
108
109227µs240µs
# spent 27µs (13+13) within Foswiki::Meta::BEGIN@109 which was called: # once (13µs+13µs) by Foswiki::BEGIN@642 at line 109
use strict;
# spent 27µs making 1 call to Foswiki::Meta::BEGIN@109 # spent 13µs making 1 call to strict::import
110224µs217µs
# spent 13µs (8+4) within Foswiki::Meta::BEGIN@110 which was called: # once (8µs+4µs) by Foswiki::BEGIN@642 at line 110
use warnings;
# spent 13µs making 1 call to Foswiki::Meta::BEGIN@110 # spent 4µs making 1 call to warnings::import
111228µs2212µs
# spent 110µs (7+102) within Foswiki::Meta::BEGIN@111 which was called: # once (7µs+102µs) by Foswiki::BEGIN@642 at line 111
use Error qw(:try);
# spent 110µs making 1 call to Foswiki::Meta::BEGIN@111 # spent 102µs making 1 call to Error::import
112226µs253µs
# spent 31µs (9+22) within Foswiki::Meta::BEGIN@112 which was called: # once (9µs+22µs) by Foswiki::BEGIN@642 at line 112
use Assert;
# spent 31µs making 1 call to Foswiki::Meta::BEGIN@112 # spent 22µs making 1 call to Exporter::import
1132120µs21.17ms
# spent 1.08ms (756µs+319µs) within Foswiki::Meta::BEGIN@113 which was called: # once (756µs+319µs) by Foswiki::BEGIN@642 at line 113
use Errno 'EINTR';
# spent 1.08ms making 1 call to Foswiki::Meta::BEGIN@113 # spent 96µs making 1 call to Exporter::import
114220µs14µs
# spent 4µs within Foswiki::Meta::BEGIN@114 which was called: # once (4µs+0s) by Foswiki::BEGIN@642 at line 114
use Encode ();
# spent 4µs making 1 call to Foswiki::Meta::BEGIN@114
115
1162105µs1609µs
# spent 609µs (490+120) within Foswiki::Meta::BEGIN@116 which was called: # once (490µs+120µs) by Foswiki::BEGIN@642 at line 116
use Foswiki::Serialise ();
# spent 609µs making 1 call to Foswiki::Meta::BEGIN@116
117
118#use Foswiki::Iterator::NumberRangeIterator;
119
120
# spent 4µs within Foswiki::Meta::BEGIN@120 which was called: # once (4µs+0s) by Foswiki::BEGIN@642 at line 125
BEGIN {
12114µs if ( $Foswiki::cfg{UseLocale} ) {
122 require locale;
123 import locale();
124 }
125139µs14µs}
# spent 4µs making 1 call to Foswiki::Meta::BEGIN@120
126
1271400nsour $VERSION = 1.2;
128
1291100nsour $reason;
130
131# Version for the embedding format (increment when embedding format changes)
1322894µs278µs
# spent 43µs (8+35) within Foswiki::Meta::BEGIN@132 which was called: # once (8µs+35µs) by Foswiki::BEGIN@642 at line 132
use constant EMBEDDING_FORMAT_VERSION => 1.1;
# spent 43µs making 1 call to Foswiki::Meta::BEGIN@132 # spent 35µs making 1 call to constant::import
133
134# defaults for truncation of summary text
1351200nsour $SUMMARY_TMLTRUNC = 162;
1361100nsour $SUMMARY_MINTRUNC = 16;
1371500nsour $SUMMARY_ELLIPSIS = '<b>&hellip;</b>'; # Google style
138
139# the number of characters either side of a search term
1401100nsour $SUMMARY_DEFAULT_CONTEXT = 30;
141
142# max number of lines in a summary (best to keep it even)
1431100nsour $CHANGES_SUMMARY_LINECOUNT = 6;
1441100nsour $CHANGES_SUMMARY_PLAINTRUNC = 70;
145
146=begin TML
147
148PUBLIC %VALIDATE;
149
150META:x validation. This hash maps from META: names to the type record
151registered by registerMETA. See registerMETA for more information on what
152these records contain.
153
154_default is set on base meta-data types (those not added by
155Foswiki::Func::registerMETA) to differentiate the minimum required
156meta-data and that added by extensions.
157
158=cut
159
160115µsour %VALIDATE = (
161 TOPICINFO => {
162 allow => [
163 qw( author version date format reprev
164 rev comment )
165 ],
166 _default => 1,
167 alias => 'info',
168 },
169 TOPICMOVED => {
170 require => [qw( from to by date )],
171 _default => 1,
172 alias => 'moved',
173 },
174
175 # Special case, see Item2554; allow an empty TOPICPARENT, as this was
176 # erroneously generated at some point in the past
177 TOPICPARENT => {
178 allow => [qw( name )],
179 _default => 1,
180 alias => 'parent',
181 },
182 FILEATTACHMENT => {
183 require => [qw( name )],
184 other => [
185 qw( version path size date user
186 comment attr )
187 ],
188 _default => 1,
189 alias => 'attachments',
190 many => 1,
191 },
192 FORM => {
193 require => [qw( name )],
194 _default => 1,
195 alias => 'form',
196 },
197 FIELD => {
198 require => [qw( name value )],
199 other => [qw( title )],
200 _default => 1,
201 alias => 'fields',
202 many => 1,
203 },
204 PREFERENCE => {
205 require => [qw( name value )],
206 other => [qw( type )],
207 _default => 1,
208 alias => 'preferences',
209 many => 1,
210 },
211 VERSIONS => {
212
213 # In trad text based data store, this does not occur in the
214 # topic text, but is pulled on demand during queries
215 alias => 'versions',
216 }
217);
218
219our %aliases =
220 map { $VALIDATE{$_}->{alias} => "META:$_" }
221114µs grep { $VALIDATE{$_}->{alias} } keys %VALIDATE;
222
223our %isArrayType =
224 map { $_ => 1 }
22515µs grep { $VALIDATE{$_}->{many} } keys %VALIDATE;
226
227=begin TML
228
229---++ StaticMethod registerMETA($name, %syntax)
230
231Foswiki supports embedding meta-data into topics. For example,
232
233=%<nop>META:BOOK{title="Transit" author="Edmund Cooper" isbn="0-571-05724-1"}%=
234
235This meta-data is validated when it is read from the store. Meta-data
236that is not registered, or doesn't pass validation, is ignored. This
237function allows you to register a new META datum, passing the name in
238=$name=. =%syntax= contains information about the syntax and semantics of
239the tag.
240
241The following entries are supported in =%syntax=
242
243=many=>1=. By default meta-data are single valued i.e. can only occur once
244in a topic. If you require the meta-data to be repeated many times (like
245META:FIELD and META:ATTACHMENT) then you must set this option. For example,
246to declare a many-valued =BOOK= meta-data type:
247<verbatim>
248registerMeta('BOOK', many => 1)
249</verbatim>
250
251=require=>[]= is used to check that a list of named parameters are present on
252the tag. For example,
253<verbatim>
254registerMETA('BOOK', require => [ 'title', 'author' ]);
255</verbatim>
256can be used to check that both =title= and =author= are present.
257
258=allow=>[]= lets you specify other optional parameters that are allowed
259on the tag. If you specify =allow= then the validation will fail if the
260tag contains any parameters that are _not_ in the =allow= or =require= lists.
261If you don't specify =allow= then all parameters will be allowed.
262
263=require= and =allow= only verify the *presence* of parameters, and
264not their *values*.
265
266=other=[]= lets you declare other legal parameters, and is provided
267mainly to support the initialisation of DB schema. It it is like
268=allow= except that it doesn't imply any exclusion of META that contains
269unallowed params.
270
271=function=>\&fn= causes the function =fn= to be called when the
272datum is encountered when reading a topic, passing in the name of the
273macro and the argument hash. The function must return a non-zero/undef
274value if the tag is acceptable, or 0 otherwise. For example:
275<verbatim>
276registerMETA('BOOK', function => sub {
277 my ($name, $args) = @_;
278 # $name will be BOOK
279 return isValidTitle($args->{title});
280}
281</verbatim>
282can be used to check that =%META:BOOK{}= contains a valid title.
283
284Checks are cumulative, so if you:
285<verbatim>
286registerMETA('BOOK',
287 function => \&checkParameters,
288 require => [ 'title' ],
289 allow => [ 'author', 'isbn' ]);
290</verbatim>
291then all these conditions will be tested. Note that =require= and =allow=
292are tested _after_ =function= is called, to give the function a chance to
293rewrite the parameter list.
294
295If no checker is registered for a META tag, then it will automatically
296be accepted into the topic meta-data.
297
298=alias=>'name'= lets you set an alias for the datum that will be added to
299the query language. For example, =alias=>'info'= is used to alias
300'META:TOPICINFO' in queries.
301<verbatim>
302registerMeta('BOOK', alias => 'book', many => 1)
303</verbatim>
304This lets you use syntax such as =books[author='Anais Nin']= in queries.
305See QuerySearch for more on aliases.
306
307=cut
308
309
# spent 6µs within Foswiki::Meta::registerMETA which was called: # once (6µs+0s) by Foswiki::Plugins::RevCommentPlugin::initPlugin at line 75 of /var/www/foswikidev/core/lib/Foswiki/Plugins/RevCommentPlugin.pm
sub registerMETA {
31012µs my ( $name, %check ) = @_;
31112µs $VALIDATE{$name} = \%check;
3121500ns $aliases{ $check{alias} } = "META:$name" if $check{alias};
31314µs $isArrayType{$name} = $check{many};
314}
315
316############# GENERIC METHODS #############
317
318=begin TML
319
320---++ ClassMethod new($session, $web, $topic [, $text])
321 * =$session= - a Foswiki object (e.g. =$Foswiki::Plugins::SESSION=)
322 * =$web=, =$topic= - the pathname of the object. If both are undef,
323 this object is a handle for the root container. If $topic is undef,
324 it is the handle to a web. Otherwise it's a handle to a topic.
325 * $text - optional raw text, which may include embedded meta-data. Will
326 be deserialised to initialise the object. Only valid
327 if =$web= and =$topic= are defined.
328Construct a new, unloaded object. This method creates lightweight
329handles for store objects; the full content of the actual object will
330*not* be loaded. If you need to interact with the existing content of
331the stored object, use the =load= method to load the content.
332
333---++ ClassMethod new($prototype)
334
335Construct a new, unloaded object, using the session, web and topic in the
336prototype object (which must be type Foswiki::Meta).
337
338=cut
339
340
# spent 353ms (318+35.0) within Foswiki::Meta::new which was called 26761 times, avg 13µs/call: # 26296 times (313ms+34.4ms) by Foswiki::Meta::load at line 449, avg 13µs/call # 80 times (762µs+100µs) by Foswiki::Store::Interfaces::QueryAlgorithm::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm:235] at line 223 of /var/www/foswikidev/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm, avg 11µs/call # 80 times (709µs+77µs) by Foswiki::Store::Interfaces::QueryAlgorithm::query at line 96 of /var/www/foswikidev/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm, avg 10µs/call # 54 times (480µs+66µs) by Foswiki::Prefs::_getBackend at line 146 of /var/www/foswikidev/core/lib/Foswiki/Prefs.pm, avg 10µs/call # 40 times (414µs+48µs) by Foswiki::Search::formatResults at line 911 of /var/www/foswikidev/core/lib/Foswiki/Search.pm, avg 12µs/call # 40 times (380µs+62µs) by Foswiki::Search::formatResults at line 743 of /var/www/foswikidev/core/lib/Foswiki/Search.pm, avg 11µs/call # 40 times (372µs+56µs) by Foswiki::Search::searchWeb at line 261 of /var/www/foswikidev/core/lib/Foswiki/Search.pm, avg 11µs/call # 40 times (342µs+47µs) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 140 of /var/www/foswikidev/core/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 10µs/call # 40 times (327µs+47µs) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 78 of /var/www/foswikidev/core/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 9µs/call # 19 times (185µs+25µs) by Foswiki::WebFilter::ok at line 50 of /var/www/foswikidev/core/lib/Foswiki/WebFilter.pm, avg 11µs/call # 12 times (103µs+15µs) by Foswiki::VAR at line 24 of /var/www/foswikidev/core/lib/Foswiki/Macros/VAR.pm, avg 10µs/call # 4 times (38µs+6µs) by Foswiki::inlineAlert at line 2617 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 11µs/call # 3 times (51µs+48µs) by Foswiki::Store::Rcs::Store::getVersionInfo at line 359 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 33µs/call # 3 times (28µs+5µs) by Foswiki::Func::expandCommonVariables at line 2656 of /var/www/foswikidev/core/lib/Foswiki/Func.pm, avg 11µs/call # 3 times (28µs+5µs) by Foswiki::REVINFO at line 34 of /var/www/foswikidev/core/lib/Foswiki/Macros/REVINFO.pm, avg 11µs/call # 3 times (29µs+-29µs) by Foswiki::Meta::new at line 348, avg 0s/call # 2 times (18µs+3µs) by Foswiki::deepWebList at line 1651 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 11µs/call # once (14µs+2µs) by Foswiki::If::OP_allows::evaluate at line 56 of /var/www/foswikidev/core/lib/Foswiki/If/OP_allows.pm # once (10µs+1µs) by Foswiki::_lookupIcon at line 36 of /var/www/foswikidev/core/lib/Foswiki/Macros/ICON.pm
sub new {
3412676115.2ms my ( $class, $session, $web, $topic, $text ) = @_;
342
34326761150ms2676135.0ms if ( $session->isa('Foswiki::Meta') ) {
# spent 35.0ms making 26761 calls to UNIVERSAL::isa, avg 1µs/call
344
345 # Prototype
346 ASSERT( !defined($web) && !defined($topic) && !defined($text) )
347 if DEBUG;
348325µs1213µs return $class->new( $session->session, $session->web, $session->topic );
# spent 5µs making 3 calls to Foswiki::Meta::web, avg 2µs/call # spent 4µs making 3 calls to Foswiki::Meta::topic, avg 1µs/call # spent 4µs making 3 calls to Foswiki::Meta::session, avg 1µs/call # spent 32µs making 3 calls to Foswiki::Meta::new, avg 11µs/call, recursion: max depth 1, sum of overlapping time 32µs
349 }
350
3512675857.6ms my $this = bless(
352 {
353 _session => $session,
354
355 # Index keyed on top level type mapping entry names to their
356 # index within the data array.
357 _indices => undef,
358 },
359 ref($class) || $class
360 );
361
362 # Normalise web path (replace [./]+ with /)
3632675810.5ms if ( defined $web ) {
364 ASSERT( UNTAINTED($web), 'web is tainted' ) if DEBUG;
3652675833.2ms $web =~ tr#/.#/#s;
366 }
367
368 # Note: internal fields are prepended with _. All uppercase
369 # fields will be assumed to be meta-data.
370
3712675819.6ms $this->{_web} = $web;
372
373 ASSERT( UNTAINTED($topic), 'topic is tainted' )
374 if ( DEBUG && defined $topic );
375
3762675811.5ms $this->{_topic} = $topic;
377
378 #print STDERR "--new Meta($web, ".($topic||'undef').")\n";
379 #$this->{_text} = undef; # topics only
380
381 # Preferences cache object. We store a pointer, rather than looking
382 # up the name each time, because we want to be able to invalidate the
383 # loaded preferences if this object is loaded.
384 #$this->{_preferences} = undef;
385
3862675818.8ms $this->{FILEATTACHMENT} = [];
387
388267584.45ms if ( defined $text ) {
389
390 # User supplied topic body forces us to consider this as the
391 # latest rev
392 ASSERT( defined($web), 'web is not defined' ) if DEBUG;
393 ASSERT( defined($topic), 'topic is not defined' ) if DEBUG;
394 Foswiki::Serialise::deserialise( $text, 'Embedded', $this );
395 $this->{_latestIsLoaded} = 1;
396 }
397
39826758112ms return $this;
399}
400
401=begin TML
402
403---++ ClassMethod load($session, $web, $topic, $rev)
404
405This constructor will load (or otherwise fetch) the meta-data for a
406named web/topic.
407 * =$rev= - revision to load. If undef, 0, '' or > max available rev, will
408 load the latest rev. If the revision is in range but does not exist,
409 then will return an unloaded meta object (getLoadedRev() will be undef)
410
411This method is functionally identical to:
412<verbatim>
413$this = Foswiki::Meta->new( $session, $web, $topic );
414$this->loadVersion( $rev );
415</verbatim>
416
417WARNING: see notes on revision numbers under =getLoadedRev=.
418
419---++ ObjectMethod load($rev) -> $metaObject
420
421Load an unloaded meta-data object with a given version of the data.
422Once loaded, the object is locked to that revision.
423
424 * =$rev= - revision to load. If undef, 0, '' or > max available rev, will
425 load the latest rev. If the revision is in range but does not exist,
426 then will return an unloaded meta object (getLoadedRev() will be undef)
427
428WARNING: see notes on revision numbers under =getLoadedRev=
429
430
431TODO: this is insane. load() can fail - but it will give you a seemingly fine Meta object anyway.
432
433=cut
434
435
# spent 77.1s (331ms+76.7) within Foswiki::Meta::load which was called 26296 times, avg 2.93ms/call: # 26280 times (331ms+76.7s) by Foswiki::MetaCache::addMeta at line 147 of /var/www/foswikidev/core/lib/Foswiki/MetaCache.pm, avg 2.93ms/call # 8 times (83µs+3.91ms) by Foswiki::_includeTopic at line 180 of /var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm, avg 499µs/call # 5 times (59µs+15.2ms) by Foswiki::Func::readTopic at line 1660 of /var/www/foswikidev/core/lib/Foswiki/Func.pm, avg 3.06ms/call # once (12µs+1.25ms) by Foswiki::Users::TopicUserMapping::_loadMapping at line 1683 of /var/www/foswikidev/core/lib/Foswiki/Users/TopicUserMapping.pm # once (10µs+555µs) by Foswiki::UI::View::view at line 129 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm # once (9µs+552µs) by Foswiki::Func::checkAccessPermission at line 1486 of /var/www/foswikidev/core/lib/Foswiki/Func.pm
sub load {
436262967.25ms my $proto = shift;
437262962.76ms my $this;
438262962.03ms my $rev;
439
4402629610.2ms if ( ref($proto) ) {
441
442 # Existing unloaded object
443 ASSERT( !$this->{_loadedRev} ) if DEBUG;
444 $this = $proto;
445 $rev = shift;
446 }
447 else {
4482629630.9ms ( my $session, my $web, my $topic, $rev ) = @_;
4492629652.6ms26296348ms $this = $proto->new( $session, $web, $topic );
# spent 348ms making 26296 calls to Foswiki::Meta::new, avg 13µs/call
450 }
451
452262968.12ms my $session = $this->{_session};
453
454# if ( defined( $this->topic )
455# and ( not defined($rev) )
456# and $this->existsInStore() )
457# {
458#SVEN: sadly, Item10805 shows that the metacache is not yet multi-user safe, and as the Groups code in TopicUserMapping changes to user=admin, we can't use it here
459#which makes it clear I need to write a full cache validation set of tests for MetaCache
460#TODO: need to extract the metacache from search, and extract the additional derived info from it too
461#NEW: the metacache has to return a _copy_ of the cached item, otherwise code that ->finish() es its copy will also ->finish() the cached version and any other refs.
462# which in Sven's opinion means we need to invert things better. (I get ~10% (.2S on 2S req's) speedup on simpler SEARCH topics doing reuse)
463# my $m =
464# $session->search->metacache->getMeta( $this->web, $this->topic );
465#
466#print STDERR "metacache->getMeta ".join(',', ( $this->web, $this->topic, ref($m) ))."\n";
467# return $m if ( defined($m) );
468# }
469
470 ASSERT( not( $this->{_latestIsLoaded} ) ) if DEBUG;
471
4722629639.3ms2629676.4s my $loadedRev = $this->loadVersion($rev);
# spent 76.4s making 26296 calls to Foswiki::Meta::loadVersion, avg 2.90ms/call
473
4742629611.2ms if ( not defined($loadedRev) ) {
475 ASSERT( not defined( $this->{_loadedRev} ) ) if DEBUG;
476
477#_latestIsloaded is mostly undef / 0 when the topic is not ondisk, except Fn_SEARCH::verify_refQuery_ForkingSearch and friends
478 ASSERT( not( $this->{_latestIsLoaded} ) ) if DEBUG;
479 }
480 else {
481 ASSERT( defined( $this->{_loadedRev} ) ) if DEBUG;
482262961.77ms ASSERT( ( $this->{_loadedRev} > 0 ),
483 "loadedRev is non-zero: $this->{_loadedRev}" )
484 if DEBUG;
485262963.36ms ASSERT( defined( $this->{_latestIsLoaded} ) ) if DEBUG;
486 }
487
4882629675.7ms return $this;
489}
490
491=begin TML
492
493---++ ObjectMethod unload()
494
495Return the object to an unloaded state. This method should be used
496with the greatest of care, as it resets the load state of the object,
497which may have surprising effects on other code that shares the object.
498
499=cut
500
501
# spent 13.0ms (82µs+12.9) within Foswiki::Meta::unload which was called 3 times, avg 4.34ms/call: # 3 times (82µs+12.9ms) by Foswiki::Meta::finish at line 533, avg 4.34ms/call
sub unload {
5023900ns my $this = shift;
503
504326µs1512.9ms $this->{_session}->search->metacache->removeMeta( $this->web, $this->topic )
# spent 12.9ms making 3 calls to Foswiki::search, avg 4.29ms/call # spent 35µs making 3 calls to Foswiki::MetaCache::removeMeta, avg 12µs/call # spent 21µs making 3 calls to Foswiki::Search::metacache, avg 7µs/call # spent 5µs making 3 calls to Foswiki::Meta::web, avg 2µs/call # spent 4µs making 3 calls to Foswiki::Meta::topic, avg 1µs/call
505 if $this->{_session};
50632µs $this->{_loadedRev} = undef;
5073500ns $this->{_latestIsLoaded} = undef;
5083500ns $this->{_text} = undef;
50931µs $this->{_preferences}->finish() if defined $this->{_preferences};
51032µs undef $this->{_preferences};
51131µs $this->{_preferences} = undef;
512
513 # Unload meta-data
51438µs foreach my $type ( keys %{ $this->{_indices} } ) {
515611µs delete $this->{$type};
516 }
51739µs undef $this->{_indices};
518}
519
520=begin TML
521
522---++ ObjectMethod finish()
523Clean up the object, releasing any memory stored in it. Make sure this
524gets called before an object you have created goes out of scope.
525
526=cut
527
528# Note to developers; please undef *all* fields in the object explicitly,
529# whether they are references or not. That way this method is "golden
530# documentation" of the live fields in the object.
531
# spent 13.0ms (20µs+13.0) within Foswiki::Meta::finish which was called 3 times, avg 4.35ms/call: # 3 times (20µs+13.0ms) by Foswiki::Store::Rcs::Store::getVersionInfo at line 363 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 4.35ms/call
sub finish {
53231µs my $this = shift;
53337µs313.0ms $this->unload();
# spent 13.0ms making 3 calls to Foswiki::Meta::unload, avg 4.34ms/call
53432µs undef $this->{_web};
5353600ns undef $this->{_topic};
5363600ns undef $this->{_session};
53737µs if (DEBUG) {
538
539 #someone keeps adding random references to Meta so to shake them out..
540 #if its an intentional ref to an object, please add it to the undef's above.
541
542#SMELL: Sven noticed during development that something is adding a $this->{store} to a meta obj - havn't found it yet
543#ASSERT(not defined($this->{store})) if DEBUG;
544
545210.2ms252µs
# spent 32µs (11+21) within Foswiki::Meta::BEGIN@545 which was called: # once (11µs+21µs) by Foswiki::BEGIN@642 at line 545
use Scalar::Util qw(blessed);
# spent 32µs making 1 call to Foswiki::Meta::BEGIN@545 # spent 21µs making 1 call to Exporter::import
546 foreach my $key (%$this) {
547
548 #ASSERT(not defined(blessed($this->{$key})));
549 }
550 }
551}
552
553=begin TML
554
555---++ ObjectMethod session()
556
557Get the session (Foswiki) object associated with the object when
558it was created.
559
560=cut
561
562
# spent 67µs within Foswiki::Meta::session which was called 49 times, avg 1µs/call: # 16 times (24µs+0s) by Foswiki::If::OP_dollar::evaluate at line 37 of /var/www/foswikidev/core/lib/Foswiki/If/OP_dollar.pm, avg 2µs/call # 13 times (16µs+0s) by Foswiki::If::OP_defined::evaluate at line 34 of /var/www/foswikidev/core/lib/Foswiki/If/OP_defined.pm, avg 1µs/call # 12 times (17µs+0s) by Foswiki::If::OP_context::evaluate at line 39 of /var/www/foswikidev/core/lib/Foswiki/If/OP_context.pm, avg 1µs/call # 4 times (5µs+0s) by Foswiki::If::OP_istopic::evaluate at line 34 of /var/www/foswikidev/core/lib/Foswiki/If/OP_istopic.pm, avg 1µs/call # 3 times (4µs+0s) by Foswiki::Meta::new at line 348, avg 1µs/call # once (1µs+0s) by Foswiki::If::OP_allows::evaluate at line 40 of /var/www/foswikidev/core/lib/Foswiki/If/OP_allows.pm
sub session {
56349129µs return $_[0]->{_session};
564}
565
566# Assert helpers
567sub _assertIsTopic {
568 my $this = shift;
569 ASSERT( $this->isa('Foswiki::Meta') );
570 ASSERT( defined $this->{_web} && $this->{_topic}, 'not a topic object' );
571}
572
573sub _assertIsWeb {
574 my $this = shift;
575 ASSERT( $this->isa('Foswiki::Meta') );
576 ASSERT( $this->{_web} && !$this->{_topic}, 'not a web object' );
577}
578
579# Does not test attachment existance, just validity of the name
580sub _assertIsAttachment {
581 my ( $this, $name ) = @_;
582 $this->_assertIsTopic();
583 ASSERT( $name, 'not a valid attachment name' );
584}
585
586=begin TML
587
588---++ ObjectMethod web([$name])
589 * =$name= - optional, change the web name in the object
590 * *Since* 28 Nov 2008
591Get/set the web name associated with the object.
592
593=cut
594
595
# spent 53.8ms within Foswiki::Meta::web which was called 35941 times, avg 1µs/call: # 26327 times (39.3ms+0s) by Foswiki::Store::Rcs::Store::readTopic at line 99 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 1µs/call # 8760 times (13.2ms+0s) by Foswiki::Search::InfoCache::addTopic at line 98 of /var/www/foswikidev/core/lib/Foswiki/Search/InfoCache.pm, avg 2µs/call # 238 times (346µs+0s) by Foswiki::innerExpandMacros at line 3068 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 1µs/call # 222 times (302µs+0s) by Foswiki::Prefs::loadPreferences at line 237 of /var/www/foswikidev/core/lib/Foswiki/Prefs.pm, avg 1µs/call # 100 times (145µs+0s) by Foswiki::expandMacros at line 3595 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 1µs/call # 100 times (144µs+0s) by Foswiki::expandMacros at line 3610 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 1µs/call # 40 times (93µs+0s) by Foswiki::SEARCH at line 19 of /var/www/foswikidev/core/lib/Foswiki/Macros/SEARCH.pm, avg 2µs/call # 40 times (58µs+0s) by Foswiki::Store::Rcs::Store::eachTopic at line 589 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 1µs/call # 18 times (33µs+0s) by Foswiki::Render::_handleSquareBracketedLink at line 1562 of /var/www/foswikidev/core/lib/Foswiki/Render.pm, avg 2µs/call # 14 times (21µs+0s) by Foswiki::Func::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Func.pm:662] at line 660 of /var/www/foswikidev/core/lib/Foswiki/Func.pm, avg 1µs/call # 12 times (18µs+0s) by Foswiki::_includeTopic at line 143 of /var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm, avg 2µs/call # 10 times (17µs+0s) by Foswiki::INCLUDE at line 403 of /var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm, avg 2µs/call # 8 times (17µs+0s) by Foswiki::_includeTopic at line 164 of /var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm, avg 2µs/call # 8 times (16µs+0s) by Foswiki::Prefs::popTopicContext at line 319 of /var/www/foswikidev/core/lib/Foswiki/Prefs.pm, avg 2µs/call # 8 times (16µs+0s) by Foswiki::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm:339] at line 206 of /var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm, avg 2µs/call # 8 times (14µs+0s) by Foswiki::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm:339] at line 317 of /var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm, avg 2µs/call # 5 times (11µs+0s) by Foswiki::Render::getRenderedVersion at line 261 of /var/www/foswikidev/core/lib/Foswiki/Render.pm, avg 2µs/call # 3 times (5µs+0s) by Foswiki::Meta::unload at line 504, avg 2µs/call # 3 times (5µs+0s) by Foswiki::REVINFO at line 20 of /var/www/foswikidev/core/lib/Foswiki/Macros/REVINFO.pm, avg 2µs/call # 3 times (5µs+0s) by Foswiki::Meta::new at line 348, avg 2µs/call # 3 times (5µs+0s) by Foswiki::REVINFO at line 29 of /var/www/foswikidev/core/lib/Foswiki/Macros/REVINFO.pm, avg 2µs/call # 2 times (4µs+0s) by Foswiki::UI::View::revisionsAround at line 547 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm, avg 2µs/call # 2 times (4µs+0s) by Foswiki::UI::View::revisionsAround at line 559 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm, avg 2µs/call # 2 times (3µs+0s) by Foswiki::Store::Rcs::Store::getRevisionHistory at line 274 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 2µs/call # 2 times (3µs+0s) by Foswiki::Store::Rcs::Store::eachWeb at line 601 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 2µs/call # once (2µs+0s) by Foswiki::Render::Parent::render at line 35 of /var/www/foswikidev/core/lib/Foswiki/Render/Parent.pm # once (2µs+0s) by Foswiki::UI::View::view at line 256 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm # once (2µs+0s) by Foswiki::Store::Rcs::Store::attachmentExists at line 230 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm
sub web {
5963594111.6ms my ( $this, $web ) = @_;
597359415.11ms $this->{_web} = $web if defined $web;
59835941109ms return $this->{_web};
599}
600
601=begin TML
602
603---++ ObjectMethod topic([$name])
604 * =$name= - optional, change the topic name in the object
605 * *Since* 28 Nov 2008
606Get/set the topic name associated with the object.
607
608=cut
609
610
# spent 70.6ms within Foswiki::Meta::topic which was called 44523 times, avg 2µs/call: # 26327 times (39.1ms+0s) by Foswiki::Store::Rcs::Store::readTopic at line 99 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 1µs/call # 8760 times (18.2ms+0s) by Foswiki::Store::Interfaces::QueryAlgorithm::getField at line 340 of /var/www/foswikidev/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm, avg 2µs/call # 8760 times (12.4ms+0s) by Foswiki::Search::InfoCache::addTopic at line 99 of /var/www/foswikidev/core/lib/Foswiki/Search/InfoCache.pm, avg 1µs/call # 238 times (312µs+0s) by Foswiki::innerExpandMacros at line 3069 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 1µs/call # 112 times (170µs+0s) by Foswiki::Prefs::loadPreferences at line 237 of /var/www/foswikidev/core/lib/Foswiki/Prefs.pm, avg 2µs/call # 100 times (142µs+0s) by Foswiki::expandMacros at line 3595 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 1µs/call # 100 times (134µs+0s) by Foswiki::expandMacros at line 3611 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 1µs/call # 40 times (71µs+0s) by Foswiki::SEARCH at line 20 of /var/www/foswikidev/core/lib/Foswiki/Macros/SEARCH.pm, avg 2µs/call # 14 times (25µs+0s) by Foswiki::Func::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Func.pm:662] at line 660 of /var/www/foswikidev/core/lib/Foswiki/Func.pm, avg 2µs/call # 12 times (19µs+0s) by Foswiki::VAR at line 19 of /var/www/foswikidev/core/lib/Foswiki/Macros/VAR.pm, avg 2µs/call # 10 times (16µs+0s) by Foswiki::INCLUDE at line 404 of /var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm, avg 2µs/call # 8 times (12µs+0s) by Foswiki::Prefs::popTopicContext at line 319 of /var/www/foswikidev/core/lib/Foswiki/Prefs.pm, avg 2µs/call # 8 times (12µs+0s) by Foswiki::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm:339] at line 206 of /var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm, avg 2µs/call # 8 times (12µs+0s) by Foswiki::_includeTopic at line 164 of /var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm, avg 2µs/call # 5 times (8µs+0s) by Foswiki::Render::getRenderedVersion at line 261 of /var/www/foswikidev/core/lib/Foswiki/Render.pm, avg 2µs/call # 3 times (5µs+0s) by Foswiki::REVINFO at line 21 of /var/www/foswikidev/core/lib/Foswiki/Macros/REVINFO.pm, avg 2µs/call # 3 times (4µs+0s) by Foswiki::Meta::unload at line 504, avg 1µs/call # 3 times (4µs+0s) by Foswiki::Meta::new at line 348, avg 1µs/call # 3 times (4µs+0s) by Foswiki::REVINFO at line 29 of /var/www/foswikidev/core/lib/Foswiki/Macros/REVINFO.pm, avg 1µs/call # 2 times (3µs+0s) by Foswiki::Store::Rcs::Store::getRevisionHistory at line 274 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 2µs/call # 2 times (3µs+0s) by Foswiki::UI::View::revisionsAround at line 559 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm, avg 2µs/call # 2 times (3µs+0s) by Foswiki::UI::View::revisionsAround at line 547 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm, avg 2µs/call # once (2µs+0s) by Foswiki::Store::Rcs::Store::attachmentExists at line 230 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm # once (2µs+0s) by Foswiki::Render::Parent::render at line 35 of /var/www/foswikidev/core/lib/Foswiki/Render/Parent.pm # once (2µs+0s) by Foswiki::UI::View::view at line 256 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm
sub topic {
6114452315.4ms my ( $this, $topic ) = @_;
612445235.69ms $this->{_topic} = $topic if defined $topic;
61344523187ms return $this->{_topic};
614}
615
616=begin TML
617
618---++ ObjectMethod getPath() -> $objectpath
619
620Get the canonical content access path for the object. For example,
621a topic "MyTopic" in subweb "Subweb" of web "Myweb" will have an
622access path "Myweb/Subweb.MyTopic"
623
624=cut
625
626
# spent 421µs within Foswiki::Meta::getPath which was called 172 times, avg 2µs/call: # 112 times (270µs+0s) by Foswiki::Prefs::loadPreferences at line 229 of /var/www/foswikidev/core/lib/Foswiki/Prefs.pm, avg 2µs/call # 54 times (130µs+0s) by Foswiki::Prefs::_getBackend at line 147 of /var/www/foswikidev/core/lib/Foswiki/Prefs.pm, avg 2µs/call # 5 times (18µs+0s) by Foswiki::Render::getAnchorNames at line 1050 of /var/www/foswikidev/core/lib/Foswiki/Render.pm, avg 4µs/call # once (3µs+0s) by Foswiki::_lookupIcon at line 53 of /var/www/foswikidev/core/lib/Foswiki/Macros/ICON.pm
sub getPath {
62717250µs my $this = shift;
62817265µs my $path = $this->{_web};
629
63017221µs return '' unless $path;
631172306µs return $path unless $this->{_topic};
6326133µs $path .= '.' . $this->{_topic};
63361125µs return $path;
634}
635
636=begin TML
637
638---++ ObjectMethod isSessionTopic() -> $boolean
639Return true if this object refers to the session topic. The session
640topic is established from the path used to invoke Foswiki, for example
641".../view/Myweb/MyTopic" sets "Myweb.MyTopic" as the session topic.
642
643=cut
644
645sub isSessionTopic {
646 my $this = shift;
647 return 0
648 unless defined $this->{_web}
649 && defined $this->{_topic}
650 && defined $this->{_session}->{webName}
651 && defined $this->{_session}->{topicName};
652 return $this->{_web} eq $this->{_session}->{webName}
653 && $this->{_topic} eq $this->{_session}->{topicName};
654}
655
656=begin TML
657
658---++ ObjectMethod getPreference( $key ) -> $value
659
660Get a value for a preference defined *in* the object. Note that
661web preferences always inherit from parent webs, but topic preferences
662are strictly local to topics.
663
664Note that this is *not* the same as =Foswiki::Func::getPreferencesValue=,
665which is almost certainly what you want to call instead.
666
667=cut
668
669
# spent 23.5ms (1.11+22.4) within Foswiki::Meta::getPreference which was called 124 times, avg 189µs/call: # 80 times (852µs+5.07ms) by Foswiki::Store::Interfaces::QueryAlgorithm::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm:235] at line 224 of /var/www/foswikidev/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm, avg 74µs/call # 19 times (121µs+16.5ms) by Foswiki::WebFilter::ok at line 51 of /var/www/foswikidev/core/lib/Foswiki/WebFilter.pm, avg 876µs/call # 13 times (70µs+331µs) by Foswiki::If::OP_defined::evaluate at line 45 of /var/www/foswikidev/core/lib/Foswiki/If/OP_defined.pm, avg 31µs/call # 12 times (71µs+442µs) by Foswiki::VAR at line 27 of /var/www/foswikidev/core/lib/Foswiki/Macros/VAR.pm, avg 43µs/call
sub getPreference {
670124129µs my ( $this, $key ) = @_;
671
67212448µs unless ( $this->{_web} || $this->{_topic} ) {
673 return $this->{_session}->{prefs}->getPreference($key);
674 }
675
676 # make sure the preferences are parsed and cached
677124389µs11219.2ms unless ( $this->{_preferences} ) {
# spent 19.2ms making 112 calls to Foswiki::Prefs::loadPreferences, avg 172µs/call
678 $this->{_preferences} =
679 $this->{_session}->{prefs}->loadPreferences($this);
680 }
681124520µs1243.15ms return $this->{_preferences}->get($key);
# spent 3.13ms making 111 calls to Foswiki::Prefs::Web::get, avg 28µs/call # spent 18µs making 13 calls to Foswiki::Prefs::TopicRAM::get, avg 1µs/call
682}
683
684=begin TML
685
686---++ ObjectMethod getContainer() -> $containerObject
687
688Get the container of this object; for example, the web that a topic is within
689
690=cut
691
692sub getContainer {
693 my $this = shift;
694
695 if ( $this->{_topic} ) {
696 return Foswiki::Meta->new( $this->{_session}, $this->{_web} );
697 }
698 if ( $this->{_web} ) {
699 return Foswiki::Meta->new( $this->{_session} );
700 }
701 ASSERT( 0, 'no container for this object type' ) if DEBUG;
702 return;
703}
704
705=begin TML
706
707---++ ObjectMethod existsInStore() -> $boolean
708
709A Meta object can be created for a web or topic that doesn't exist in the
710actual store (e.g. is in the process of being created). This method returns
711true if the corresponding web or topic really exists in the store.
712
713=cut
714
715
# spent 2.01ms (182µs+1.82) within Foswiki::Meta::existsInStore which was called 29 times, avg 69µs/call: # 29 times (182µs+1.82ms) by Foswiki::Prefs::TopicRAM::new at line 38 of /var/www/foswikidev/core/lib/Foswiki/Prefs/TopicRAM.pm, avg 69µs/call
sub existsInStore {
716296µs my $this = shift;
7172914µs if ( defined $this->{_topic} ) {
718
719 # only checking for a topic existence already establishes a dependency
7202925µs2943µs $this->addDependency();
# spent 43µs making 29 calls to Foswiki::Meta::addDependency, avg 1µs/call
721
72229100µs291.78ms return $this->{_session}->{store}
# spent 1.78ms making 29 calls to Foswiki::Store::Rcs::Store::topicExists, avg 61µs/call
723 ->topicExists( $this->{_web}, $this->{_topic} );
724 }
725 elsif ( defined $this->{_web} ) {
726 return $this->{_session}->{store}->webExists( $this->{_web} );
727 }
728 else {
729 return 1; # the root always exists
730 }
731}
732
733=begin TML
734
735---++ ObjectMethod stringify( $debug ) -> $string
736
737Return a string version of the meta object. $debug adds
738extra debug info.
739
740=cut
741
742sub stringify {
743 my ( $this, $debug ) = @_;
744 my $s = $this->{_web};
745 if ( $this->{_topic} ) {
746 $s .= ".$this->{_topic} ";
747 $s .=
748 ( defined $this->{_loadedRev} )
749 ? $this->{_loadedRev}
750 : '(not loaded)'
751 if $debug;
752 $s .= "\n" . Foswiki::Serialise::serialise( $this, 'Embedded' );
753 }
754 return $s;
755}
756
757=begin TML
758
759---++ ObjectMethod addDependency() -> $this
760
761This establishes a caching dependency between $this and the
762base topic this session is currently rendering. The dependency
763will be asserted during Foswiki::PageCache::cachePage().
764See Foswiki::PageCache::addDependency().
765
766=cut
767
768
# spent 46.3ms within Foswiki::Meta::addDependency which was called 26356 times, avg 2µs/call: # 26327 times (46.2ms+0s) by Foswiki::Meta::loadVersion at line 1164, avg 2µs/call # 29 times (43µs+0s) by Foswiki::Meta::existsInStore at line 720, avg 1µs/call
sub addDependency {
7692635622.7ms my $cache = $_[0]->{_session}->{cache};
77026356122ms return unless $cache;
771 return $cache->addDependency( $_[0]->{_web}, $_[0]->{_topic} );
772}
773
774=begin TML
775
776---++ ObjectMethod fireDependency() -> $this
777
778Invalidates the cache bucket of the current meta object
779within the Foswiki::PageCache. See Foswiki::PageCache::fireDependency().
780
781=cut
782
783sub fireDependency {
784 my $cache = $_[0]->{_session}->{cache};
785 return unless $cache;
786 return $cache->fireDependency( $_[0]->{_web}, $_[0]->{_topic} );
787}
788
789=begin TML
790
791---++ ObjectMethod isCacheable() -> $boolean
792
793Return true if page caching is enabled and this topic object is cacheable.
794
795=cut
796
797
# spent 645µs within Foswiki::Meta::isCacheable which was called 384 times, avg 2µs/call: # 100 times (172µs+0s) by Foswiki::expandMacros at line 3606 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 2µs/call # 100 times (166µs+0s) by Foswiki::expandMacros at line 3652 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 2µs/call # 87 times (145µs+0s) by Foswiki::_processMacros at line 3252 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 2µs/call # 87 times (141µs+0s) by Foswiki::_processMacros at line 3369 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 2µs/call # 5 times (10µs+0s) by Foswiki::Render::getRenderedVersion at line 559 of /var/www/foswikidev/core/lib/Foswiki/Render.pm, avg 2µs/call # 5 times (10µs+0s) by Foswiki::Render::getRenderedVersion at line 231 of /var/www/foswikidev/core/lib/Foswiki/Render.pm, avg 2µs/call
sub isCacheable {
79838490µs my $this = shift;
799
800384917µs return 0 unless $Foswiki::cfg{Cache}{Enabled};
801
802 my $cache = $this->{_session}->{cache};
803 return 0 unless $cache;
804
805 return $cache->isCacheable( $this->{_web}, $this->{_topic} );
806}
807
808############# WEB METHODS #############
809
810=begin TML
811
812---++ ObjectMethod populateNewWeb( [$baseWeb [, $opts]] )
813
814$baseWeb is the name of an existing web (a template web). If the
815base web is a system web, all topics in it
816will be copied into this web. If it is a normal web, only topics starting
817with 'Web' will be copied. If no base web is specified, an empty web
818(with no topics) will be created. If it is specified but does not exist,
819an error will be thrown.
820
821$opts is a ref to a hash that contains settings to be modified in
822the web preferences topic in the new web.
823
824=cut
825
826# SMELL: there seems to be no reason to call this method 'NewWeb', it can
827# be used to copy into an existing web and it does not appear to be
828# unexpectedly destructive.
829# perhaps refactor into something that takes a resultset as an input list?
830# (users have asked to be able to copy a SEARCH'd set of topics..)
831sub populateNewWeb {
832 my ( $this, $templateWeb, $opts ) = @_;
833 _assertIsWeb($this) if DEBUG;
834
835 my $session = $this->{_session};
836
837 my ( $parent, $new ) = $this->{_web} =~ m/^(.*)\/([^\.\/]+)$/;
838
839 if ($parent) {
840 unless ( $Foswiki::cfg{EnableHierarchicalWebs} ) {
841 throw Error::Simple( 'Unable to create '
842 . $this->{_web}
843 . ' - Hierarchical webs are disabled' );
844 }
845
846 unless ( $session->webExists($parent) ) {
847 throw Error::Simple( 'Parent web ' . $parent . ' does not exist' );
848 }
849 }
850
851 # Validate that template web exists, or error should be thrown
852 if ($templateWeb) {
853 unless ( $session->webExists($templateWeb) ) {
854 throw Error::Simple(
855 'Template web ' . $templateWeb . ' does not exist' );
856 }
857 }
858
859 # Make sure there is a preferences topic; this is how we know it's a web
860 my $prefsTopicObject;
861 if (
862 !$session->topicExists(
863 $this->{_web}, $Foswiki::cfg{WebPrefsTopicName}
864 )
865 )
866 {
867 my $prefsText = 'Preferences';
868 $prefsTopicObject =
869 $this->new( $this->{_session}, $this->{_web},
870 $Foswiki::cfg{WebPrefsTopicName}, $prefsText );
871 $prefsTopicObject->save();
872 }
873
874 if ($templateWeb) {
875 my $tWebObject = $this->new( $session, $templateWeb );
876 require Foswiki::WebFilter;
877 my $sys =
878 Foswiki::WebFilter->new('template')->ok( $session, $templateWeb );
879 my $it = $tWebObject->eachTopic();
880 while ( $it->hasNext() ) {
881 my $topic = $it->next();
882 next unless ( $sys || $topic =~ m/^Web/ );
883 my $to =
884 Foswiki::Meta->load( $this->{_session}, $templateWeb, $topic );
885
886 # Open attachment filehandles
887 my %attfh;
888 foreach my $sfa ( $to->find('FILEATTACHMENT') ) {
889 my $fh = $to->openAttachment( $sfa->{name}, '<' );
890 $attfh{ $sfa->{name} } = {
891 fh => $fh,
892 date => $sfa->{date},
893 user => $sfa->{user} || $session->{user},
894 comment => $sfa->{comment}
895 };
896 }
897 $to->saveAs(
898 web => $this->{_web},
899 topic => $topic,
900 forcenewrevision => 1
901 );
902
903 # copy fileattachments
904 while ( my ( $fa, $sfa ) = each %attfh ) {
905 my $arev = $session->{store}->saveAttachment(
906 $to, $fa,
907 $sfa->{fh},
908 $sfa->{user},
909 {
910 forcedate => $sfa->{date},
911 minor => 1,
912 comment => $sfa->{comment}
913 }
914 );
915 close( $sfa->{fh} );
916 ASSERT($arev) if DEBUG;
917 $this->{_session}->{store}->recordChange(
918 verb => 'insert',
919 cuid => $sfa->{user},
920 revision => $to->{_loadedRev},
921 path => $to->getPath(),
922 attachment => $fa,
923 comment => "add $arev"
924 );
925 }
926 }
927 }
928
929 # patch WebPreferences in new web. We ignore permissions, because
930 # we are creating a new web here.
931 if ($opts) {
932 my $prefsTopicObject =
933 Foswiki::Meta->load( $this->{_session}, $this->{_web},
934 $Foswiki::cfg{WebPrefsTopicName} );
935 my $text = $prefsTopicObject->text;
936 foreach my $key ( keys %$opts ) {
937
938 #don't create the required params to create web.
939 next if ( $key eq 'BASEWEB' );
940 next if ( $key eq 'NEWWEB' );
941 next if ( $key eq 'NEWTOPIC' );
942 next if ( $key eq 'ACTION' );
943
944 if ( defined( $opts->{$key} ) ) {
945 if ( $text =~
946s/($Foswiki::regex{setRegex}$key\s*=).*?$/$1 $opts->{$key}/gm
947 )
948 {
949 }
950 else {
951
952 #this setting wasn't found, so we need to append it.
953 $text .= "\n * Web Created with KEY set\n";
954 $text .= "\n * Set $key = $opts->{$key}\n";
955 }
956 }
957 }
958 $prefsTopicObject->text($text);
959 $prefsTopicObject->save();
960 }
961}
962
963=begin TML
964
965---++ StaticMethod query($query, $inputTopicSet, \%options) -> $outputTopicSet
966
967Search for topic information
968=$query= must be a =Foswiki::*::Node= object.
969
970 * $inputTopicSet is a reference to an iterator containing a list
971 of topic in this web, if set to undef, the search/query algo will
972 create a new iterator using eachTopic()
973 and the web, topic and excludetopics options (as per SEARCH)
974 * web option - The web/s to search in - string can have the same form
975 as the =web= param of SEARCH
976
977
978Returns an Foswiki::Search::InfoCache iterator
979
980=cut
981
982
# spent 108s (391µs+108) within Foswiki::Meta::query which was called 40 times, avg 2.71s/call: # 40 times (391µs+108s) by Foswiki::Search::searchWeb at line 379 of /var/www/foswikidev/core/lib/Foswiki/Search.pm, avg 2.71s/call
sub query {
9834048µs my ( $query, $inputTopicSet, $options ) = @_;
98440332µs40108s return $Foswiki::Plugins::SESSION->{store}
# spent 108s making 40 calls to Foswiki::Store::Rcs::Store::query, avg 2.71s/call
985 ->query( $query, $inputTopicSet, $Foswiki::Plugins::SESSION, $options );
986}
987
988=begin TML
989
990---++ ObjectMethod eachWeb( $all ) -> $iterator
991
992Return an iterator over each subweb. If =$all= is set, will return a
993list of all web names *under* the current location. Returns web pathnames
994relative to $this.
995
996Only valid on webs and the root.
997
998=cut
999
1000
# spent 17.9ms (16µs+17.9) within Foswiki::Meta::eachWeb which was called 2 times, avg 8.96ms/call: # 2 times (16µs+17.9ms) by Foswiki::deepWebList at line 1652 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 8.96ms/call
sub eachWeb {
100122µs my ( $this, $all ) = @_;
1002
1003 # Works on the root, so {_web} may be undef
1004 ASSERT( !$this->{_topic}, 'this object may not contain webs' ) if DEBUG;
1005214µs217.9ms return $this->{_session}->{store}->eachWeb( $this, $all );
# spent 17.9ms making 2 calls to Foswiki::Store::Rcs::Store::eachWeb, avg 8.95ms/call
1006
1007}
1008
1009=begin TML
1010
1011---++ ObjectMethod eachTopic() -> $iterator
1012
1013Return an iterator over each topic name in the web. Only valid on webs.
1014
1015=cut
1016
1017
# spent 2.79s (313µs+2.79) within Foswiki::Meta::eachTopic which was called 40 times, avg 69.8ms/call: # 40 times (313µs+2.79s) by Foswiki::Search::InfoCache::getTopicListIterator at line 479 of /var/www/foswikidev/core/lib/Foswiki/Search/InfoCache.pm, avg 69.8ms/call
sub eachTopic {
10184030µs my ($this) = @_;
1019 _assertIsWeb($this) if DEBUG;
10204039µs if ( !$this->{_web} ) {
1021
1022 # Root
1023 require Foswiki::ListIterator;
1024 return new Foswiki::ListIterator( [] );
1025 }
102640286µs402.79s return $this->{_session}->{store}->eachTopic($this);
# spent 2.79s making 40 calls to Foswiki::Store::Rcs::Store::eachTopic, avg 69.8ms/call
1027}
1028
1029=begin TML
1030
1031---++ ObjectMethod eachAttachment() -> $iterator
1032
1033Return an iterator over each attachment name in the topic.
1034Only valid on topics.
1035
1036The list of the names of attachments stored for the given topic may be a
1037longer list than the list that comes from the topic meta-data, which may
1038only lists the attachments that are normally visible to the user.
1039
1040=cut
1041
1042sub eachAttachment {
1043 my ($this) = @_;
1044 _assertIsTopic($this) if DEBUG;
1045 return $this->{_session}->{store}->eachAttachment($this);
1046}
1047
1048=begin TML
1049
1050---++ ObjectMethod eachChange( $time ) -> $iterator
1051
1052Get an iterator over the list of all the changes to the object between
1053=$time= and now. $time is a time in seconds since 1st Jan 1970, and is not
1054guaranteed to return any changes that occurred before (now -
1055{Store}{RememberChangesFor}). Changes are returned in most-recent-first
1056order.
1057
1058If the object is a web, changes for all topics within that web will be
1059iterated. If it's a topic, then all changes to the topic will be iterated.
1060
1061Each element of the iterator is a reference to a hash:
1062 * =verb= - the action - one of
1063 * =update= - a web, topic or attachment has been modified
1064 * =insert= - a web, topic or attachment is being inserted
1065 * =remove= - a topic or attachment is being removed
1066 * =time= - epoch-secs time of the change
1067 * =cuid= - who is making the change
1068 * =revision= - the revision of the topic that the change appears in
1069 * =path= - canonical web.topic path for the affected object
1070 * =attachment= - attachment name (optional)
1071 * =oldpath= - canonical web.topic path for the origin of a move/rename
1072 * =oldattachment= - origin of move
1073 * =minor= - boolean true if this change is flagged as minor
1074 * =comment= - descriptive text
1075
1076Stores must return the following fields if compatibility with Foswiki < 2
1077is required.
1078 * =topic= - name of the topic the change occurred to
1079 * =more= - formatted string indicating if the change was minor or not
1080 * =user= - wikiname of the changing user
1081The fields are *deprecated* and should not be used by core.
1082
1083=cut
1084
1085sub eachChange {
1086 my ( $this, $time ) = @_;
1087
1088 # not valid at root level
1089 _assertIsWeb($this) if DEBUG;
1090 return $this->{_session}->{store}->eachChange( $this, $time );
1091}
1092
1093############# TOPIC METHODS #############
1094
1095=begin TML
1096
1097---++ ObjectMethod loadVersion($rev) -> $version
1098
1099Load the object from the store. The object must not be already loaded
1100with a different rev (verified by an ASSERT)
1101
1102See =getLoadedRev= to determine what revision is currently being viewed.
1103 * =$rev= - revision to load. If undef, 0, '' or > max available rev, will
1104 load the latest rev. If the revision is in range but does not exist,
1105 then will return an unloaded meta object (getLoadedRev() will be undef)
1106
1107Returns the version identifier for the loaded revision. (and undef if it failed to load)
1108
1109WARNING: see notes on revision numbers under =getLoadedRev=
1110
1111=cut
1112
1113
# spent 76.4s (512ms+75.9) within Foswiki::Meta::loadVersion which was called 26327 times, avg 2.90ms/call: # 26296 times (512ms+75.9s) by Foswiki::Meta::load at line 472, avg 2.90ms/call # 28 times (270µs+33.7ms) by Foswiki::Meta::text at line 1196, avg 1.21ms/call # 3 times (30µs+1.65ms) by Foswiki::Store::Rcs::Store::getVersionInfo at line 360 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 560µs/call
sub loadVersion {
11142632712.8ms my ( $this, $rev ) = @_;
1115
1116263276.36ms return unless $this->{_topic};
1117
1118 # If no specific rev was requested, check that the latest rev is
1119 # loaded.
11202632714.8ms if ( !defined $rev || !$rev ) {
1121
1122 # Trying to load the latest
1123 if ( $this->{_latestIsLoaded} ) {
1124
1125 #TODO: these asserts trip up Comment Plugin
1126 #ASSERT(defined($this->{_loadedRev})) if DEBUG;
1127 #ASSERT($rev == $this->{_loadedRev}) if DEBUG;
1128 return;
1129 }
1130
1131 # SMELL: Sven added this assert, but i don't understand why and
1132 # it causes PlainFile to fail for no good reason. C.
1133 #ASSERT( not( $this->{_loadedRev} ), $this->{_loadedRev} ) if DEBUG;
1134 }
1135 elsif ( defined( $this->{_loadedRev} ) ) {
1136
1137 # Cannot load a different rev into an already-loaded
1138 # Foswiki::Meta object
1139 $rev = -1 unless defined $rev;
1140 ASSERT( 0, "Attempt to reload $rev over version $this->{_loadedRev}" )
1141 if DEBUG;
1142 }
1143
1144 # Is it already loaded?
1145 ASSERT( !($rev) or $rev =~ m/^\s*\d+\s*/ ) if DEBUG; # looks like a number
1146263273.12ms return $this->{_loadedRev}
1147 if ( $rev && $this->{_loadedRev} && $rev == $this->{_loadedRev} );
1148
1149 # SMELL: Sven added this assert, but i don't understand why and
1150 # it causes PlainFile to fail for no good reason. C.
1151 #ASSERT( not( $this->{_loadedRev} ) ) if DEBUG;
1152
1153 # Note: Since Item12472, the store implementation is expected
1154 # to call setLoadStatus() in readTopic
11552632731.5ms2632748.6ms $this->setLoadStatus( undef, undef );
# spent 48.6ms making 26327 calls to Foswiki::Meta::setLoadStatus, avg 2µs/call
11562632746.3ms2632775.8s $this->{_session}->{store}->readTopic( $this, $rev );
# spent 75.8s making 26327 calls to Foswiki::Store::Rcs::Store::readTopic, avg 2.88ms/call
1157
11582632712.8ms if ( defined( $this->{_loadedRev} ) ) {
1159
1160 # Make sure text always has a value once loadVersion has been called
1161 # once.
1162263275.11ms $this->{_text} = '' unless defined $this->{_text};
1163
11642632737.7ms2632746.2ms $this->addDependency();
# spent 46.2ms making 26327 calls to Foswiki::Meta::addDependency, avg 2µs/call
1165 }
1166 else {
1167
1168 #we didn't load, so how could it be latest?
1169 ASSERT( not $this->{_latestIsLoaded} ) if DEBUG;
1170 }
1171
11722632791.8ms return $this->{_loadedRev};
1173}
1174
1175=begin TML
1176
1177---++ ObjectMethod text([$text]) -> $text
1178
1179Get/set the topic body text. If $text is undef, gets the value, if it is
1180defined, sets the value to that and returns the new text.
1181
1182Be warned - it can return undef - when a topic exists but has no topicText.
1183
1184=cut
1185
1186
# spent 138ms (104+33.8) within Foswiki::Meta::text which was called 35131 times, avg 4µs/call: # 26327 times (75.9ms+-136µs) by Foswiki::Serialise::Embedded::read at line 166 of /var/www/foswikidev/core/lib/Foswiki/Serialise/Embedded.pm, avg 3µs/call # 8760 times (27.6ms+0s) by Foswiki::Search::formatResults at line 789 of /var/www/foswikidev/core/lib/Foswiki/Search.pm, avg 3µs/call # 29 times (173µs+34.0ms) by Foswiki::Prefs::Parser::parse at line 46 of /var/www/foswikidev/core/lib/Foswiki/Prefs/Parser.pm, avg 1.18ms/call # 8 times (23µs+0s) by Foswiki::__ANON__[/var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm:339] at line 211 of /var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm, avg 3µs/call # 5 times (25µs+0s) by Foswiki::Func::readTopic at line 1662 of /var/www/foswikidev/core/lib/Foswiki/Func.pm, avg 5µs/call # once (19µs+0s) by Foswiki::Users::TopicUserMapping::_loadMapping at line 1688 of /var/www/foswikidev/core/lib/Foswiki/Users/TopicUserMapping.pm # once (4µs+0s) by Foswiki::UI::View::view at line 223 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm
sub text {
11873513118.9ms my ( $this, $val ) = @_;
1188 _assertIsTopic($this) if DEBUG;
11893513132.0ms if ( defined($val) ) {
1190 $this->{_text} = $val;
1191 }
1192 else {
1193
1194 # Lazy load. Reload with no params will reload the _loadedRev,
1195 # or load the latest if that is not defined.
119688043.86ms2834.0ms $this->loadVersion() unless defined( $this->{_text} );
# spent 34.0ms making 28 calls to Foswiki::Meta::loadVersion, avg 1.21ms/call
1197 }
119835131142ms return $this->{_text};
1199}
1200
1201=begin TML
1202
1203---++ ObjectMethod put($type, \%args)
1204
1205Put a hash of key=value pairs into the given type set in this meta. This
1206will *not* replace another value with the same name (for that see =putKeyed=)
1207
1208For example,
1209<verbatim>
1210$meta->put( 'FIELD', { name => 'MaxAge', title => 'Max Age', value =>'103' } );
1211</verbatim>
1212
1213=cut
1214
1215
# spent 329ms within Foswiki::Meta::put which was called 52396 times, avg 6µs/call: # 52390 times (329ms+0s) by Foswiki::Serialise::Embedded::_readMETA at line 194 of /var/www/foswikidev/core/lib/Foswiki/Serialise/Embedded.pm, avg 6µs/call # 3 times (18µs+0s) by Foswiki::Store::Rcs::Store::getVersionInfo at line 362 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 6µs/call # 3 times (12µs+0s) by Foswiki::Meta::setRevisionInfo at line 1533, avg 4µs/call
sub put {
12165239626.5ms my ( $this, $type, $args ) = @_;
1217 _assertIsTopic($this) if DEBUG;
1218523963.57ms ASSERT( defined $type ) if DEBUG;
1219 ASSERT( defined $args && ref($args) eq 'HASH' ) if DEBUG;
1220
12215239638.4ms unless ( $this->{$type} ) {
12225239357.6ms $this->{$type} = [];
12235239351.2ms $this->{_indices}->{$type} = {};
1224 }
1225
12265239620.1ms my $data = $this->{$type};
1227523967.90ms my $i = 0;
12285239614.0ms if ($data) {
1229
1230 # overwrite old single value
12315239611.9ms if ( scalar(@$data) && defined $data->[0]->{name} ) {
1232 delete $this->{_indices}->{$type}->{ $data->[0]->{name} };
1233 }
12345239635.3ms $data->[0] = $args;
1235 }
1236 else {
1237 $i = push( @$data, $args ) - 1;
1238 }
123952396151ms if ( defined $args->{name} ) {
1240 $this->{_indices}->{$type} ||= {};
1241 $this->{_indices}->{$type}->{ $args->{name} } = $i;
1242 }
1243}
1244
1245=begin TML
1246
1247---++ ObjectMethod putKeyed($type, \%args)
1248
1249Put a hash of key=value pairs into the given type set in this meta, replacing
1250any existing value with the same key.
1251
1252For example,
1253<verbatim>
1254$meta->putKeyed( 'FIELD',
1255 { name => 'MaxAge', title => 'Max Age', value =>'103' } );
1256</verbatim>
1257
1258=cut
1259
1260# Note: Array is used instead of a hash to preserve sequence
1261
1262
# spent 4.02s within Foswiki::Meta::putKeyed which was called 875082 times, avg 5µs/call: # 875069 times (4.02s+0s) by Foswiki::Serialise::Embedded::_readMETA at line 188 of /var/www/foswikidev/core/lib/Foswiki/Serialise/Embedded.pm, avg 5µs/call # 13 times (60µs+0s) by Foswiki::Compatibility::readSymmetricallyEncodedMETA at line 367 of /var/www/foswikidev/core/lib/Foswiki/Compatibility.pm, avg 5µs/call
sub putKeyed {
1263875082417ms my ( $this, $type, $args ) = @_;
1264 _assertIsTopic($this) if DEBUG;
126587508257.4ms ASSERT($type) if DEBUG;
1266 ASSERT( $args && ref($args) eq 'HASH' ) if DEBUG;
1267875082241ms my $keyName = $args->{name};
1268 ASSERT( $keyName, join( ',', keys %$args ) ) if DEBUG;
1269
1270875082353ms unless ( $this->{$type} ) {
12717886561.8ms $this->{$type} = [];
12727886558.7ms $this->{_indices}->{$type} = {};
1273 }
1274
1275875082227ms my $data = $this->{$type};
1276
1277 # The \% shouldn't be necessary, but it is
1278875082474ms my $indices = \%{ $this->{_indices}->{$type} };
12798750823.29s if ( defined $indices->{$keyName} ) {
1280 $data->[ $indices->{$keyName} ] = $args;
1281 }
1282 else {
1283875082939ms $indices->{$keyName} = push( @$data, $args ) - 1;
1284 }
1285}
1286
1287=begin TML
1288
1289---++ ObjectMethod putAll
1290
1291Replaces all the items of a given key with a new array.
1292
1293For example,
1294<verbatim>
1295$meta->putAll( 'FIELD',
1296 { name => 'MinAge', title => 'Min Age', value =>'50' },
1297 { name => 'MaxAge', title => 'Max Age', value =>'103' },
1298 { name => 'HairColour', title => 'Hair Colour', value =>'white' }
1299 );
1300</verbatim>
1301
1302=cut
1303
1304sub putAll {
1305 my ( $this, $type, @array ) = @_;
1306 _assertIsTopic($this) if DEBUG;
1307
1308 my %indices;
1309 for ( my $i = 0 ; $i < scalar(@array) ; $i++ ) {
1310 if ( defined $array[$i]->{name} ) {
1311 $indices{ $array[$i]->{name} } = $i;
1312 }
1313 }
1314 $this->{$type} = \@array;
1315 $this->{_indices}->{$type} = \%indices;
1316}
1317
1318=begin TML
1319
1320---++ ObjectMethod get( $type, $key ) -> \%hash
1321
1322Find the value of a meta-datum in the map. If the type is
1323keyed (identified by a =name=), the =$key= parameter is required
1324to say _which_ entry you want. Otherwise you will just get the first value.
1325
1326If you want all the keys of a given type use the 'find' method.
1327
1328The result is a reference to the hash for the item.
1329
1330For example,
1331<verbatim>
1332my $ma = $meta->get( 'FIELD', 'MinAge' );
1333my $topicinfo = $meta->get( 'TOPICINFO' ); # get the TOPICINFO hash
1334</verbatim>
1335
1336=cut
1337
1338
# spent 183ms within Foswiki::Meta::get which was called 61651 times, avg 3µs/call: # 26327 times (69.8ms+0s) by Foswiki::Serialise::Embedded::read at line 79 of /var/www/foswikidev/core/lib/Foswiki/Serialise/Embedded.pm, avg 3µs/call # 26304 times (78.5ms+0s) by Foswiki::Store::Rcs::Store::readTopic at line 140 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 3µs/call # 8760 times (34.3ms+0s) by Foswiki::Store::Interfaces::QueryAlgorithm::getField at line 350 of /var/www/foswikidev/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm, avg 4µs/call # 222 times (460µs+0s) by Foswiki::Meta::getRevisionInfo at line 1591, avg 2µs/call # 29 times (56µs+0s) by Foswiki::Prefs::Parser::parse at line 85 of /var/www/foswikidev/core/lib/Foswiki/Prefs/Parser.pm, avg 2µs/call # 3 times (8µs+0s) by Foswiki::Meta::setRevisionInfo at line 1519, avg 3µs/call # 3 times (6µs+0s) by Foswiki::Store::Rcs::Store::getVersionInfo at line 361 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 2µs/call # once (2µs+0s) by Foswiki::Render::Parent::render at line 44 of /var/www/foswikidev/core/lib/Foswiki/Render/Parent.pm # once (2µs+0s) by Foswiki::Plugins::AutoViewTemplatePlugin::initPlugin at line 51 of /var/www/foswikidev/core/lib/Foswiki/Plugins/AutoViewTemplatePlugin.pm # once (2µs+0s) by Foswiki::Meta::getFormName at line 1803
sub get {
13396165133.9ms my ( $this, $type, $name ) = @_;
1340 _assertIsTopic($this) if DEBUG;
1341
13426165123.6ms my $data = $this->{$type};
13436165113.5ms if ($data) {
13446161512.7ms if ( defined $name ) {
1345
134687602.09ms my $indices = $this->{_indices};
134787601.05ms return undef unless defined $indices;
134887602.59ms $indices = $indices->{$type};
13498760807µs return undef unless defined $indices;
135087603.95ms return undef unless defined $indices->{$name};
1351876034.1ms return $data->[ $indices->{$name} ];
1352 }
1353 else {
135452855226ms return $data->[0];
1355 }
1356 }
1357
13583681µs return undef;
1359}
1360
1361=begin TML
1362
1363---++ ObjectMethod find ( $type ) -> @values
1364
1365Get all meta data for a specific type.
1366Returns the array stored for the type. This will be zero length
1367if there are no entries.
1368
1369For example,
1370<verbatim>
1371my $attachments = $meta->find( 'FILEATTACHMENT' );
1372</verbatim>
1373
1374=cut
1375
1376
# spent 92µs within Foswiki::Meta::find which was called 30 times, avg 3µs/call: # 29 times (86µs+0s) by Foswiki::Prefs::Parser::parse at line 75 of /var/www/foswikidev/core/lib/Foswiki/Prefs/Parser.pm, avg 3µs/call # once (6µs+0s) by Foswiki::Attach::renderMetaData at line 96 of /var/www/foswikidev/core/lib/Foswiki/Attach.pm
sub find {
13773015µs my ( $this, $type ) = @_;
1378 _assertIsTopic($this) if DEBUG;
1379
13803012µs my $itemsr = $this->{$type};
13813017µs my @items = ();
1382
1383309µs if ($itemsr) {
1384 @items = @$itemsr;
1385 }
1386
13873074µs return @items;
1388}
1389
1390=begin TML
1391
1392---++ ObjectMethod remove($type, $key)
1393
1394With no type, will remove all the meta-data in the object.
1395
1396With a $type but no $key, will remove _all_ items of that type
1397(so for example if $type were FILEATTACHMENT it would remove all of them)
1398
1399With a $type and a $key it will remove only the specific item.
1400
1401=cut
1402
1403sub remove {
1404 my ( $this, $type, $name ) = @_;
1405 _assertIsTopic($this) if DEBUG;
1406
1407 if ($type) {
1408 my $data = $this->{$type};
1409 return unless defined $data;
1410 if ($name) {
1411 my $indices = $this->{_indices}->{$type};
1412 if ( defined $indices ) {
1413 my $i = $indices->{$name};
1414 return unless defined $i;
1415 splice( @$data, $i, 1 );
1416 delete $indices->{$name};
1417 for ( my $i = 0 ; $i < scalar(@$data) ; $i++ ) {
1418 my $item = $data->[$i];
1419 next unless exists $item->{name};
1420 $indices->{ $item->{name} } = $i;
1421 }
1422 }
1423 }
1424 else {
1425 delete $this->{$type};
1426 delete $this->{_indices}->{$type};
1427 }
1428 }
1429 else {
1430 foreach my $entry ( keys %$this ) {
1431 unless ( $entry =~ m/^_/ ) {
1432 delete $this->{$entry};
1433 }
1434 }
1435 $this->{_indices} = {};
1436 }
1437}
1438
1439=begin TML
1440
1441---++ ObjectMethod copyFrom( $otherMeta [, $type [, $nameFilter]] )
1442
1443Copy all entries of a type from another meta data set. This
1444will destroy the old values for that type, unless the
1445copied object doesn't contain entries for that type, in which
1446case it will retain the old values.
1447
1448If $type is undef, will copy ALL TYPES.
1449
1450If $nameFilter is defined (a perl regular expression), it will copy
1451only data where ={name}= matches $nameFilter.
1452
1453Does *not* copy web, topic or text.
1454
1455=cut
1456
1457sub copyFrom {
1458 my ( $this, $other, $type, $filter ) = @_;
1459 _assertIsTopic($this) if DEBUG;
1460 _assertIsTopic($other) if DEBUG;
1461
1462 if ($type) {
1463 return if $type =~ m/^_/;
1464 my @data;
1465 foreach my $item ( @{ $other->{$type} } ) {
1466 if ( !$filter
1467 || ( $item->{name} && $item->{name} =~ m/$filter/ ) )
1468 {
1469 ASSERT( defined($item) ) if DEBUG;
1470 my %datum = %$item;
1471 push( @data, \%datum );
1472 }
1473 }
1474 $this->putAll( $type, @data );
1475 }
1476 else {
1477 foreach my $k ( keys %$other ) {
1478 unless ( $k =~ m/^_/ ) {
1479 $this->copyFrom( $other, $k );
1480 }
1481 }
1482 }
1483}
1484
1485=begin TML
1486
1487---++ ObjectMethod count($type) -> $integer
1488
1489Return the number of entries of the given type
1490
1491=cut
1492
1493
# spent 57.6ms within Foswiki::Meta::count which was called 26327 times, avg 2µs/call: # 26327 times (57.6ms+0s) by Foswiki::Serialise::Embedded::read at line 125 of /var/www/foswikidev/core/lib/Foswiki/Serialise/Embedded.pm, avg 2µs/call
sub count {
14942632712.1ms my ( $this, $type ) = @_;
1495 _assertIsTopic($this) if DEBUG;
14962632710.3ms my $data = $this->{$type};
1497
14982632795.4ms return scalar(@$data) if ( defined($data) );
1499
150013µs return 0;
1501}
1502
1503=begin TML
1504
1505---++ ObjectMethod setRevisionInfo( %opts )
1506
1507Set TOPICINFO information on the object, as specified by the parameters.
1508 * =version= - the revision number
1509 * =time= - the time stamp
1510 * =author= - the user id (cUID)
1511 * + additional data fields to save e.g. reprev, comment
1512
1513=cut
1514
1515
# spent 86µs (66+19) within Foswiki::Meta::setRevisionInfo which was called 3 times, avg 29µs/call: # 3 times (66µs+19µs) by Foswiki::Meta::getRevisionInfo at line 1606, avg 29µs/call
sub setRevisionInfo {
1516310µs my ( $this, %data ) = @_;
1517 _assertIsTopic($this) if DEBUG;
1518
151935µs38µs my $ti = $this->get('TOPICINFO') || {};
# spent 8µs making 3 calls to Foswiki::Meta::get, avg 3µs/call
1520
152137µs foreach my $k ( keys %data ) {
15222114µs $ti->{$k} = $data{$k};
1523 }
1524
1525 # compatibility; older versions of the code use
1526 # RCS rev numbers. Save with them so old code can
1527 # read these topics
1528 ASSERT( defined $ti->{version} ) if DEBUG;
152934µs $ti->{version} = 1 if $ti->{version} < 1;
153032µs $ti->{version} = $ti->{version};
153132µs $ti->{format} = EMBEDDING_FORMAT_VERSION;
1532
1533314µs312µs $this->put( 'TOPICINFO', $ti );
# spent 12µs making 3 calls to Foswiki::Meta::put, avg 4µs/call
1534}
1535
1536=begin TML
1537
1538---++ ObjectMethod getRevisionInfo([$attachment [,$rev]]) -> \%info
1539
1540 * =$attachment= - (optional) attachment name to get info about
1541 * =$rev= - (optional) revision of attachment for which to get info
1542
1543Return revision info for the loaded revision of a topic or
1544attachment with at least:
1545 * ={date}= in epochSec
1546 * ={author}= canonical user ID
1547 * ={version}= the revision number
1548
1549---++ ObjectMethod getRevisionInfo() -> ( $revDate, $author, $rev, $comment )
1550
1551Limited backwards compatibility for plugins that assume the 1.0.x interface
1552The comment is *always* blank
1553
1554=cut
1555
1556
# spent 17.4ms (1.59+15.8) within Foswiki::Meta::getRevisionInfo which was called 222 times, avg 78µs/call: # 219 times (1.52ms+452µs) by Foswiki::MetaCache::get at line 230 of /var/www/foswikidev/core/lib/Foswiki/MetaCache.pm, avg 9µs/call # 3 times (73µs+15.3ms) by Foswiki::Render::renderRevisionInfo at line 802 of /var/www/foswikidev/core/lib/Foswiki/Render.pm, avg 5.13ms/call
sub getRevisionInfo {
1557222102µs my ( $this, $attachment, $rev ) = @_;
1558
1559 _assertIsTopic($this) if DEBUG;
1560
156122242µs if ($attachment) {
1562 return $this->{_session}->{store}
1563 ->getVersionInfo( $this, $rev, $attachment );
1564 }
1565
156622223µs my $info;
156722271µs3279µs if ( not defined( $this->{_loadedRev} )
# spent 279µs making 3 calls to Foswiki::Func::topicExists, avg 93µs/call
1568 and not Foswiki::Func::topicExists( $this->{_web}, $this->{_topic} ) )
1569 {
1570
1571#print STDERR "topic does not exist - at least, _loadedRev is not set..(".$this->{_web} .' '. $this->{_topic}.")\n";
1572#this does not exist on disk - no reason to goto the store for the defaults
1573#TODO: Sven is not 100% sure this is the right decision, but it feels better not to do a trip into the deep for an application default
1574 $info = {
1575 date => 0,
1576 author => $Foswiki::Users::BaseUserMapping::DEFAULT_USER_CUID,
1577 version => 0,
1578 format => EMBEDDING_FORMAT_VERSION,
1579 };
1580 return $info;
1581 }
1582
1583 # This used to try and get revision info from the meta
1584 # information and only kick down to the Store module for the
1585 # same information if it was not present. However there have
1586 # been several cases where the meta information in the cache
1587 # is badly out of step with the store, and the conclusion is
1588 # that it can't be trusted. For this reason, when meta is read
1589 # TOPICINFO version field is automatically undefined, which
1590 # forces this function to re-get it from the store.
1591222225µs222460µs my $topicinfo = $this->get('TOPICINFO');
# spent 460µs making 222 calls to Foswiki::Meta::get, avg 2µs/call
1592
1593222492µs if ( $topicinfo && defined $topicinfo->{version} ) {
1594 $info = {
1595 date => $topicinfo->{date},
1596 author => $topicinfo->{author},
1597 version => $topicinfo->{version},
1598 };
1599 }
1600 else {
1601
1602 # Delegate to the store
1603315µs314.9ms $info = $this->{_session}->{store}->getVersionInfo($this);
# spent 14.9ms making 3 calls to Foswiki::Store::Rcs::Store::getVersionInfo, avg 4.98ms/call
1604
1605 # cache the result
1606312µs386µs $this->setRevisionInfo(%$info);
# spent 86µs making 3 calls to Foswiki::Meta::setRevisionInfo, avg 29µs/call
1607 }
1608
160922265µs if (wantarray) {
1610
1611 # Backwards compatibility for 1.0.x plugins
1612 return ( $info->{date}, $info->{author}, $info->{version}, '' );
1613 }
1614 else {
1615222439µs return $info;
1616 }
1617}
1618
1619# Determines, and caches, the topic revision info of the base version,
1620# SMELL: this is a horrid little legacy of the InfoCache object, and
1621# should be done away with.
1622sub getRev1Info {
1623 my ( $this, $attr ) = @_;
1624 _assertIsTopic($this) if DEBUG;
1625
1626#my ( $web, $topic ) = Foswiki::Func::normalizeWebTopicName( $this->{_defaultWeb}, $webtopic );
1627 my $web = $this->web;
1628 my $topic = $this->topic;
1629
1630 if ( !defined( $this->{_getRev1Info} ) ) {
1631 $this->{_getRev1Info} = {};
1632 }
1633 my $info = $this->{_getRev1Info};
1634 unless ( defined $info->{$attr} ) {
1635 my $ri = $info->{rev1info};
1636 unless ($ri) {
1637 my $tmp = Foswiki::Meta->load( $this->{_session}, $web, $topic, 1 );
1638 $info->{rev1info} = $ri = $tmp->getRevisionInfo();
1639 }
1640
1641 if ( $attr eq 'createusername' ) {
1642 $info->{createusername} =
1643 $this->{_session}->{users}->getLoginName( $ri->{author} );
1644 }
1645 elsif ( $attr eq 'createwikiname' ) {
1646 $info->{createwikiname} =
1647 $this->{_session}->{users}->getWikiName( $ri->{author} );
1648 }
1649 elsif ( $attr eq 'createwikiusername' ) {
1650 $info->{createwikiusername} =
1651 $this->{_session}->{users}->webDotWikiName( $ri->{author} );
1652 }
1653 elsif ($attr eq 'createdate'
1654 or $attr eq 'createlongdate'
1655 or $attr eq 'created' )
1656 {
1657 $info->{created} = $ri->{date};
1658
1659 # Don't pass Foswiki::Time an undef value
1660 if ( defined $ri->{date} ) {
1661 require Foswiki::Time;
1662 $info->{createdate} = Foswiki::Time::formatTime( $ri->{date} );
1663
1664 #TODO: wow thats disgusting.
1665 $info->{created} = $info->{createlongdate} =
1666 $info->{createdate};
1667 }
1668 }
1669 }
1670 return $info->{$attr};
1671}
1672
1673=begin TML
1674
1675---++ ObjectMethod merge( $otherMeta, $formDef )
1676
1677 * =$otherMeta= - a block of meta-data to merge with $this
1678 * =$formDef= reference to a Foswiki::Form that gives the types of the fields in $this
1679
1680Merge the data in the other meta block.
1681 * File attachments that only appear in one set are preserved.
1682 * Form fields that only appear in one set are preserved.
1683 * Form field values that are different in each set are text-merged
1684 * We don't merge for field attributes or title
1685 * Topic info is not touched
1686 * The =isTextMergeable= method on the form def is used to determine if that field is mergeable. If it isn't, the value currently in meta will _not_ be changed.
1687
1688=cut
1689
1690sub merge {
1691 my ( $this, $other, $formDef ) = @_;
1692 _assertIsTopic($this) if DEBUG;
1693 _assertIsTopic($other) if DEBUG;
1694
1695 my $data = $other->{FIELD};
1696 if ($data) {
1697 foreach my $otherD (@$data) {
1698 my $thisD = $this->get( 'FIELD', $otherD->{name} );
1699 if ( $thisD && $thisD->{value} ne $otherD->{value} ) {
1700 if ( $formDef->isTextMergeable( $thisD->{name} ) ) {
1701 require Foswiki::Merge;
1702 my $merged = Foswiki::Merge::merge2(
1703 'A',
1704 $otherD->{value},
1705 'B',
1706 $thisD->{value},
1707 '.*?\s+',
1708 $this->{_session},
1709 $formDef->getField( $thisD->{name} )
1710 );
1711
1712 # SMELL: we don't merge attributes or title
1713 $thisD->{value} = $merged;
1714 }
1715 }
1716 elsif ( !$thisD ) {
1717 $this->putKeyed( 'FIELD', $otherD );
1718 }
1719 }
1720 }
1721
1722 $data = $other->{FILEATTACHMENT};
1723 if ($data) {
1724 foreach my $otherD (@$data) {
1725 my $thisD = $this->get( 'FILEATTACHMENT', $otherD->{name} );
1726 if ( !$thisD ) {
1727 $this->putKeyed( 'FILEATTACHMENT', $otherD );
1728 }
1729 }
1730 }
1731}
1732
1733=begin TML
1734
1735---++ ObjectMethod forEachSelectedValue( $types, $keys, \&fn, \%options )
1736
1737Iterate over the values selected by the regular expressions in $types and
1738$keys.
1739 * =$types= - regular expression matching the names of fields to be processed. Will default to qr/^[A-Z]+$/ if undef.
1740 * =$keys= - regular expression matching the names of keys to be processed. Will default to qr/^[a-z]+$/ if undef.
1741
1742Iterates over each value, calling =\&fn= on each, and replacing the value
1743with the result of \&fn.
1744
1745\%options will be passed on to $fn, with the following additions:
1746 * =_type= => the type name (e.g. "FILEATTACHMENT")
1747 * =_key= => the key name (e.g. "user")
1748
1749=cut
1750
1751sub forEachSelectedValue {
1752 my ( $this, $types, $keys, $fn, $options ) = @_;
1753 _assertIsTopic($this) if DEBUG;
1754
1755 $types ||= qr/^[A-Z]+$/;
1756 $keys ||= qr/^[a-z]+$/;
1757
1758 foreach my $type ( grep { /$types/ } keys %$this ) {
1759 $options->{_type} = $type;
1760 my $data = $this->{$type};
1761 next unless $data;
1762 foreach my $datum (@$data) {
1763 foreach my $key ( grep { /$keys/ } keys %$datum ) {
1764 $options->{_key} = $key;
1765 $datum->{$key} = &$fn( $datum->{$key}, $options );
1766 }
1767 }
1768 }
1769}
1770
1771=begin TML
1772
1773---++ ObjectMethod getParent() -> $parent
1774
1775Gets the TOPICPARENT name. Safe shortcut for =$meta->get('TOPICPARENT')->{name}
1776Returns the emty string if there is no parent.
1777
1778=cut
1779
1780sub getParent {
1781 my ($this) = @_;
1782
1783 my $value = '';
1784 my $parent = $this->get('TOPICPARENT');
1785 $value = $parent->{name} if ($parent);
1786
1787 # Return empty string (not undef), if TOPICPARENT meta is broken
1788 $value = '' if ( !defined $value );
1789 return $value;
1790}
1791
1792=begin TML
1793
1794---++ ObjectMethod getFormName() -> $formname
1795
1796Returns the name of the FORM, or '' if none.
1797
1798=cut
1799
1800
# spent 11µs (10+2) within Foswiki::Meta::getFormName which was called: # once (10µs+2µs) by Foswiki::Meta::renderFormForDisplay at line 1823
sub getFormName {
18011500ns my ($this) = @_;
1802
180311µs12µs my $aForm = $this->get('FORM');
# spent 2µs making 1 call to Foswiki::Meta::get
18041200ns if ($aForm) {
1805 return $aForm->{name};
1806 }
180716µs return '';
1808}
1809
1810=begin TML
1811
1812---++ ObjectMethod renderFormForDisplay() -> $html
1813
1814Render the form contained in the meta for display.
1815
1816=cut
1817
1818# SMELL: this is part of the View and should be moved closer to the renderer
1819
# spent 5.92ms (2.97+2.95) within Foswiki::Meta::renderFormForDisplay which was called: # once (2.97ms+2.95ms) by Foswiki::META at line 40 of /var/www/foswikidev/core/lib/Foswiki/Macros/META.pm
sub renderFormForDisplay {
18201500ns my ($this) = @_;
1821 _assertIsTopic($this) if DEBUG;
1822
182313µs111µs my $fname = $this->getFormName();
# spent 11µs making 1 call to Foswiki::Meta::getFormName
1824
18251116µs require Foswiki::Form;
182611µs require Foswiki::OopsException;
182718µs return '' unless $fname;
1828
1829 my $form;
1830 my $result;
1831 try {
1832 $form = new Foswiki::Form( $this->{_session}, $this->{_web}, $fname );
1833 $result = $form->renderForDisplay($this);
1834 }
1835 catch Foswiki::OopsException with {
1836
1837 # Make pseudo-form from field data
1838 $form =
1839 new Foswiki::Form( $this->{_session}, $this->{_web}, $fname, $this );
1840 $result =
1841 $this->{_session}->inlineAlert( 'alerts', 'formdef_missing', $fname );
1842 $result .= $form->renderForDisplay($this) if $form;
1843 };
1844
1845 return $result;
1846}
1847
1848=begin TML
1849
1850---++ ObjectMethod renderFormFieldForDisplay($name, $format, $attrs) -> $text
1851
1852Render a single formfield, using the $format. See
1853Foswiki::Form::FormField::renderForDisplay for a description of how the value
1854is rendered.
1855
1856=cut
1857
1858# SMELL: this is part of the View and should be moved closer to the renderer
1859sub renderFormFieldForDisplay {
1860 my ( $this, $name, $format, $attrs ) = @_;
1861 _assertIsTopic($this) if DEBUG;
1862
1863 my $mf = $this->get( 'FIELD', $name );
1864 unless ($mf) {
1865
1866 # Not a valid field name, maybe it's a title.
1867 require Foswiki::Form;
1868 $name = Foswiki::Form::fieldTitle2FieldName($name);
1869 $mf = $this->get( 'FIELD', $name );
1870 }
1871 return '' unless $mf; # field not found
1872
1873 my $fname = $this->getFormName();
1874 if ($fname) {
1875 require Foswiki::Form;
1876 my $result;
1877 try {
1878 my $form =
1879 new Foswiki::Form( $this->{_session}, $this->{_web}, $fname );
1880 my $field = $form->getField($name);
1881 if ($field) {
1882 $attrs->{usetitle} = $mf->{title};
1883 $result =
1884 $field->renderForDisplay( $format, $mf->{value}, $attrs );
1885 }
1886 }
1887 catch Foswiki::OopsException with {
1888
1889 # Form not found, ignore
1890 };
1891
1892 return $result if defined $result;
1893 }
1894
1895 # Form or field wasn't found, do your best!
1896 my $f = $this->get( 'FIELD', $name );
1897 if ($f) {
1898 $format =~ s/\$title/$f->{title}/;
1899 require Foswiki::Render;
1900 my $value =
1901 Foswiki::Render::protectFormFieldValue( $mf->{value}, $attrs );
1902 $format =~ s/\$value(\([^)]*\))?/$value/;
1903 }
1904 return $format;
1905}
1906
1907=begin TML
1908
1909---++ ObjectMethod haveAccess($mode, $cUID) -> $boolean
1910
1911 * =$mode= - 'VIEW', 'CHANGE', 'CREATE', etc. (defaults to VIEW)
1912 * =$cUID= - Canonical user id (defaults to current user)
1913Check if the user has the given mode of access to the topic. This call
1914may result in the topic being read.
1915
1916=cut
1917
1918
# spent 14.2ms (2.79+11.4) within Foswiki::Meta::haveAccess which was called 252 times, avg 56µs/call: # 219 times (2.41ms+3.15ms) by Foswiki::MetaCache::get at line 244 of /var/www/foswikidev/core/lib/Foswiki/MetaCache.pm, avg 25µs/call # 19 times (200µs+254µs) by Foswiki::WebFilter::ok at line 59 of /var/www/foswikidev/core/lib/Foswiki/WebFilter.pm, avg 24µs/call # 8 times (85µs+112µs) by Foswiki::_includeTopic at line 183 of /var/www/foswikidev/core/lib/Foswiki/Macros/INCLUDE.pm, avg 25µs/call # 3 times (45µs+72µs) by Foswiki::REVINFO at line 39 of /var/www/foswikidev/core/lib/Foswiki/Macros/REVINFO.pm, avg 39µs/call # once (21µs+7.78ms) by Foswiki::Func::checkAccessPermission at line 1501 of /var/www/foswikidev/core/lib/Foswiki/Func.pm # once (16µs+27µs) by Foswiki::If::OP_allows::evaluate at line 57 of /var/www/foswikidev/core/lib/Foswiki/If/OP_allows.pm # once (12µs+17µs) by Foswiki::UI::checkAccess at line 633 of /var/www/foswikidev/core/lib/Foswiki/UI.pm
sub haveAccess {
1919252127µs my ( $this, $mode, $cUID ) = @_;
192025233µs $mode ||= 'VIEW';
1921252151µs $cUID ||= $this->{_session}->{user};
1922
192325264µs my $session = $this->{_session};
1924
1925252601µs50410.8ms my $ok = $session->access->haveAccess( $mode, $cUID, $this );
# spent 8.14ms making 252 calls to Foswiki::access, avg 32µs/call # spent 2.63ms making 252 calls to Foswiki::Access::TopicACLAccess::haveAccess, avg 10µs/call
1926252545µs504640µs $reason = $session->access->getReason();
# spent 342µs making 252 calls to Foswiki::access, avg 1µs/call # spent 299µs making 252 calls to Foswiki::Access::getReason, avg 1µs/call
1927252533µs return $ok;
1928}
1929
1930=begin TML
1931
1932---++ ObjectMethod save( %options )
1933
1934Save the current object, invoking appropriate plugin handlers
1935 * =%options= - Hash of options, see saveAs for list of keys
1936
1937=cut
1938
1939# SMELL: arguably save should only be permitted if the loaded rev of
1940# the object is the same as the latest rev.
1941sub save {
1942 my $this = shift;
1943 ASSERT( scalar(@_) % 2 == 0 ) if DEBUG;
1944 my %opts = @_;
1945 _assertIsTopic($this) if DEBUG;
1946
1947 my $plugins = $this->{_session}->{plugins};
1948
1949 # make sure version and date in TOPICINFO are up-to-date
1950 # (side effect of getRevisionInfo)
1951 $this->getRevisionInfo();
1952
1953 # Semantics inherited from Cairo. See
1954 # Foswiki:Codev.BugBeforeSaveHandlerBroken
1955 if ( !$opts{nohandlers} && $plugins->haveHandlerFor('beforeSaveHandler') ) {
1956
1957 # Break up the tom and write the meta into the topic text.
1958 # Nasty compatibility requirement as some old plugins may hack the
1959 # meta instead of using the Meta API
1960 my $text = Foswiki::Serialise::serialise( $this, 'Embedded' );
1961
1962 my $pretext = $text; # text before the handler modifies it
1963 my $premeta = $this->stringify(); # just the meta, no text
1964 unless ( $this->{_loadedRev} ) {
1965
1966 # The meta obj doesn't have a loaded rev yet, and we have to block the
1967 # beforeSaveHandlers from loading the topic from store. We are saving,
1968 # and anything we have in $this is going to get written anyway, so we
1969 # can simply mark it as "the latest".
1970 # SMELL: this may not work if the beforeSaveHandler tries to use the
1971 # meta obj for access control checks, so that is not recommended.
1972 $this->{_loadedRev} = $this->getLatestRev();
1973 }
1974
1975 $plugins->dispatch( 'beforeSaveHandler', $text, $this->{_topic},
1976 $this->{_web}, $this );
1977
1978 # If the text has changed; it may be a text or meta change, or both
1979 if ( $text ne $pretext ) {
1980
1981 # Create a new object to parse the changed text
1982 my $after =
1983 new Foswiki::Meta( $this->{_session}, $this->{_web},
1984 $this->{_topic}, $text );
1985 unless ( $this->stringify() ne $premeta ) {
1986
1987 # Meta-data changes in the object take priority over
1988 # conflicting changes in the text. So if there have been
1989 # *any* changes in the meta, ignore changes in the text.
1990 $this->copyFrom($after);
1991 }
1992 $this->text( $after->text() );
1993 }
1994 }
1995
1996 my $signal;
1997 my $newRev;
1998 try {
1999 $newRev = $this->saveAs(%opts);
2000 }
2001 catch Error with {
2002 $signal = shift;
2003 };
2004
2005 # Semantics inherited from TWiki. See
2006 # TWiki:Codev.BugBeforeSaveHandlerBroken
2007 if ( !$opts{nohandlers}
2008 && !defined $signal
2009 && $plugins->haveHandlerFor('afterSaveHandler') )
2010 {
2011 my $text = Foswiki::Serialise::serialise( $this, 'Embedded' );
2012 delete $this->{_preferences}; # Make sure handler has changed prefs
2013 my $error = $signal ? $signal->{-text} : undef;
2014 $plugins->dispatch( 'afterSaveHandler', $text, $this->{_topic},
2015 $this->{_web}, $error, $this );
2016 }
2017
2018 throw $signal if $signal;
2019
2020 ASSERT( $newRev, $this->{_loadedRev} ) if DEBUG;
2021
2022 my @extras = ();
2023 push( @extras, 'minor' ) if $opts{minor}; # don't notify
2024 push( @extras, 'dontlog' ) if $opts{dontlog}; # don't statisticify
2025
2026 $this->{_session}->logger->log(
2027 {
2028 level => 'info',
2029 action => 'save',
2030 webTopic => $this->{_web} . '.' . $this->{_topic},
2031 extra => join( ', ', @extras ),
2032 user => $this->{_session}->{user},
2033 }
2034 );
2035
2036 return $newRev;
2037}
2038
2039=begin TML
2040
2041---++ ObjectMethod saveAs( $web, $topic, %options ) -> $rev
2042
2043Save the current topic to a store location. Only works on topics.
2044*without* invoking plugins handlers.
2045 * =$web.$topic= - where to move to (defaults to web.topic in the object)
2046 * =%options= - Hash of options, may include:
2047 * =forcenewrevision= - force an increment in the revision number,
2048 even if content doesn't change.
2049 * =dontlog= - don't include this change in statistics
2050 * =minor= - don't notify this change
2051 * =savecmd= - Save command (core use only)
2052 * =forcedate= - force the revision date to be this (core only)
2053 * =author= - cUID of author of change (core only - default current user)
2054 * =nohandlers= - *do not* call plugins handlers
2055
2056Note that the %options are passed on verbatim from Foswiki::Func::saveTopic,
2057so an extension author can in fact use all these options. However those
2058marked "core only" are for core use only and should *not* be used in
2059extensions.
2060
2061Returns the saved revision number.
2062
2063=cut
2064
2065# SMELL: arguably save should only be permitted if the loaded rev
2066# of the object is the same as the latest rev.
2067sub saveAs {
2068 my ( $this, %opts ) = @_;
2069 _assertIsTopic($this) if DEBUG;
2070
2071 $this->{_web} = $opts{web} if $opts{web};
2072 $this->{_topic} = $opts{topic} if $opts{topic};
2073
2074 my $cUID = $opts{author} || $this->{_session}->{user};
2075 _assertIsTopic($this) if DEBUG;
2076
2077 unless ( $this->{_topic} eq $Foswiki::cfg{WebPrefsTopicName} ) {
2078
2079 # Don't verify web existance for WebPreferences, as saving
2080 # WebPreferences creates the web.
2081 unless ( $this->{_session}->{store}->webExists( $this->{_web} ) ) {
2082 throw Error::Simple( 'Unable to save topic '
2083 . $this->{_topic}
2084 . ' - web '
2085 . $this->{_web}
2086 . ' does not exist' );
2087 }
2088 }
2089
2090 $this->_atomicLock($cUID);
2091 my $i = $this->{_session}->{store}->getRevisionHistory($this);
2092 my $currentRev = $i->hasNext() ? $i->next() : 1;
2093 try {
2094 if ( $currentRev && !$opts{forcenewrevision} ) {
2095
2096 # See if we want to replace the existing top revision
2097 my $mtime1 =
2098 $this->{_session}->{store}
2099 ->getApproxRevTime( $this->{_web}, $this->{_topic} );
2100 my $mtime2 = time();
2101 my $dt = abs( $mtime2 - $mtime1 );
2102 if ( $dt <= $Foswiki::cfg{ReplaceIfEditedAgainWithin} ) {
2103 my $info = $this->{_session}->{store}->getVersionInfo($this);
2104
2105 # same user?
2106 if ( $info->{author} eq $cUID ) {
2107
2108 # reprev is required so we can tell when a merge is
2109 # based on something that is *not* the original rev
2110 # where another users' edit started.
2111 $info->{reprev} = $info->{version};
2112 $info->{date} = $opts{forcedate} || time();
2113 $this->setRevisionInfo(%$info);
2114 $this->{_session}->{store}->repRev( $this, $cUID, %opts );
2115 $this->{_loadedRev} = $currentRev;
2116 $this->{_session}->{store}->recordChange(
2117 verb => 'update',
2118 cuid => $cUID,
2119 revision => $currentRev,
2120 path => $this->getPath(),
2121 minor => 1,
2122 comment => 'reprev',
2123 );
2124 return $currentRev;
2125 }
2126 }
2127 }
2128 my $nextRev = $this->{_session}->{store}->getNextRevision($this);
2129 $this->setRevisionInfo(
2130 date => $opts{forcedate} || time(),
2131 author => $cUID,
2132 version => $nextRev,
2133 );
2134
2135 my $checkSave =
2136 $this->{_session}->{store}->saveTopic( $this, $cUID, \%opts );
2137 ASSERT( $checkSave == $nextRev, "$checkSave != $nextRev" ) if DEBUG;
2138 $this->{_loadedRev} = $nextRev;
2139 $this->{_latestIsLoaded} = 1;
2140
2141 $this->{_session}->{store}->recordChange(
2142 cuid => $cUID,
2143 revision => $nextRev,
2144 verb => $nextRev == 1 ? 'insert' : 'update',
2145 path => $this->getPath(),
2146 minor => $opts{minor},
2147 );
2148 }
2149 finally {
2150 $this->_atomicUnlock($cUID);
2151 $this->fireDependency();
2152 };
2153 return $this->{_loadedRev};
2154}
2155
2156# An atomic lock will cause other
2157# processes that also try to claim a lock to block. A lock has a
2158# maximum lifetime of 2 minutes, so operations on a locked topic
2159# must be completed within that time. You cannot rely on the
2160# lock timeout clearing the lock, though; that should always
2161# be done by calling _atomicUnlock. The best thing to do is to guard
2162# the locked section with a try..finally clause. See man Error for more info.
2163#
2164# Atomic locks are _not_ the locks used when a topic is edited; those are
2165# Leases.
2166
2167sub _atomicLock {
2168 my ( $this, $cUID ) = @_;
2169 if ( $this->{_topic} ) {
2170 my $logger = $this->{_session}->logger();
2171 while (1) {
2172 my ( $user, $time ) =
2173 $this->{_session}->{store}->atomicLockInfo($this);
2174 last if ( !$user || $cUID eq $user );
2175 $logger->log( 'warning',
2176 'Lock on '
2177 . $this->getPath() . ' for '
2178 . $cUID
2179 . " denied by $user" );
2180
2181 # see how old the lock is. If it's older than 2 minutes,
2182 # break it anyway. Locks are atomic, and should never be
2183 # held that long, by _any_ process.
2184 if ( time() - $time > 2 * 60 ) {
2185 $logger->log( 'warning',
2186 $cUID . " broke ${user}s lock on " . $this->getPath() );
2187 $this->{_session}->{store}->atomicUnlock( $this, $cUID );
2188 last;
2189 }
2190
2191 # wait a couple of seconds before trying again
2192 sleep(2);
2193 }
2194
2195 # Topic
2196 $this->{_session}->{store}->atomicLock( $this, $cUID );
2197 }
2198 else {
2199
2200 # Web: Recursively lock subwebs and topics
2201 my $it = $this->eachWeb();
2202 while ( $it->hasNext() ) {
2203 my $web = $this->{_web} . '/' . $it->next();
2204 my $meta = $this->new( $this->{_session}, $web );
2205 $meta->_atomicLock($cUID);
2206 }
2207 $it = $this->eachTopic();
2208 while ( $it->hasNext() ) {
2209 my $meta =
2210 $this->new( $this->{_session}, $this->{_web}, $it->next() );
2211 $meta->_atomicLock($cUID);
2212 }
2213 }
2214}
2215
2216sub _atomicUnlock {
2217 my ( $this, $cUID ) = @_;
2218 if ( $this->{_topic} ) {
2219 $this->{_session}->{store}->atomicUnlock($this);
2220 }
2221 else {
2222 my $it = $this->eachWeb();
2223 while ( $it->hasNext() ) {
2224 my $web = $this->{_web} . '/' . $it->next();
2225 my $meta = $this->new( $this->{_session}, $web );
2226 $meta->_atomicUnlock($cUID);
2227 }
2228 $it = $this->eachTopic();
2229 while ( $it->hasNext() ) {
2230 my $meta =
2231 $this->new( $this->{_session}, $this->{_web}, $it->next() );
2232 $meta->_atomicUnlock($cUID);
2233 }
2234 }
2235}
2236
2237=begin TML
2238
2239---++ ObjectMethod move($to, %opts)
2240
2241Move this object (web or topic) to a store location specified by the
2242object $to. %opts may include:
2243 * =user= - cUID of the user doing the moving.
2244
2245=cut
2246
2247# will assert false if the loaded rev of the object is not
2248# the latest rev.
2249sub move {
2250 my ( $this, $to, %opts ) = @_;
2251 ASSERT( $this->{_web}, 'this is not a movable object' ) if DEBUG;
2252 ASSERT( $to->isa('Foswiki::Meta') && $to->{_web},
2253 'to is not a moving target' )
2254 if DEBUG;
2255
2256 my $cUID = $opts{user} || $this->{_session}->{user};
2257
2258 if ( $this->{_topic} ) {
2259
2260 # Move topic
2261
2262 $this->_atomicLock($cUID);
2263 $to->_atomicLock($cUID);
2264
2265 # Ensure latest rev is loaded
2266 my $from;
2267 if ( $this->latestIsLoaded() ) {
2268 $from = $this;
2269 }
2270 else {
2271 $from = $this->load();
2272 }
2273
2274 # Clear outstanding leases. We assume that the caller has checked
2275 # that the lease is OK to kill.
2276 $from->clearLease() if $from->getLease();
2277 try {
2278 $from->put(
2279 'TOPICMOVED',
2280 {
2281 from => $from->getPath(),
2282 to => $to->getPath(),
2283 date => time(),
2284 by => $cUID,
2285 }
2286 );
2287
2288 # save the metadata change without logging
2289 $this->saveAs(
2290 dontlog => 1, # no statistics
2291 );
2292 $from->{_session}->{store}->moveTopic( $from, $to, $cUID );
2293 $to->loadVersion();
2294 ASSERT( defined($to) and defined( $to->{_loadedRev} ) ) if DEBUG;
2295 $this->{_session}->{store}->recordChange(
2296 cuid => $cUID,
2297 revision => $to->{_loadedRev},
2298 verb => 'update',
2299 oldpath => $from->getPath(),
2300 path => $to->getPath()
2301 );
2302
2303 }
2304 finally {
2305 $from->_atomicUnlock($cUID);
2306 $to->_atomicUnlock($cUID);
2307 $from->fireDependency();
2308 $to->fireDependency();
2309 };
2310
2311 }
2312 else {
2313
2314 # Move web
2315 ASSERT( !$this->{_session}->{store}->webExists( $to->{_web} ),
2316 "$to->{_web} does not exist" )
2317 if DEBUG;
2318 $this->_atomicLock($cUID);
2319 $this->{_session}->{store}->moveWeb( $this, $to, $cUID );
2320
2321 # Record the web move as a move of the WebPreferences topic
2322 my $from =
2323 Foswiki::Meta->load( $this->{_session}, $this->web,
2324 $Foswiki::cfg{WebPrefsTopicName} );
2325 my $to =
2326 Foswiki::Meta->load( $this->{_session}, $to->web,
2327 $Foswiki::cfg{WebPrefsTopicName} );
2328 $this->{_session}->{store}->recordChange(
2329 cuid => $cUID,
2330 revision => $to->{_loadedRev},
2331 verb => 'update',
2332 oldpath => $from->getPath(),
2333 path => $to->getPath(),
2334 comment => 'moved_web'
2335 );
2336
2337 # No point in unlocking $this - it's moved!
2338 $to->_atomicUnlock($cUID);
2339 }
2340
2341 # Log rename
2342 my $old = $this->{_web} . '.' . ( $this->{_topic} || '' );
2343 my $new = $to->{_web} . '.' . ( $to->{_topic} || '' );
2344 $this->{_session}->logger->log(
2345 {
2346 level => 'info',
2347 action => 'rename',
2348 webTopic => $old,
2349 extra => "moved to $new",
2350 user => $this->{_session}->{user}
2351 }
2352 );
2353
2354 # alert plugins of topic move
2355 $this->{_session}->{plugins}
2356 ->dispatch( 'afterRenameHandler', $this->{_web}, $this->{_topic} || '',
2357 '', $to->{_web}, $to->{_topic} || '', '' );
2358}
2359
2360=begin TML
2361
2362---++ ObjectMethod deleteMostRecentRevision(%opts)
2363Delete (or elide) the most recent revision of this. Only works on topics.
2364
2365=%opts= may include
2366 * =user= - cUID of user doing the unlocking
2367
2368=cut
2369
2370sub deleteMostRecentRevision {
2371 my ( $this, %opts ) = @_;
2372 _assertIsTopic($this) if DEBUG;
2373 my $rev;
2374 my $cUID = $opts{user} || $this->{_session}->{user};
2375
2376 $this->_atomicLock($cUID);
2377 try {
2378 $rev = $this->{_session}->{store}->delRev( $this, $cUID );
2379 $this->{_session}->{store}->recordChange(
2380 cuid => $cUID,
2381 revision => $rev,
2382 verb => 'update',
2383 path => $this->getPath
2384 );
2385
2386 }
2387 finally {
2388 $this->_atomicUnlock($cUID);
2389 $this->fireDependency();
2390 };
2391
2392 # TODO: delete entry in .changes
2393
2394 # write log entry
2395 $this->{_session}->logger->log(
2396 {
2397 level => 'info',
2398 action => 'cmd',
2399 webTopic => $this->{_web} . '.' . $this->{_topic},
2400 extra => "delRev $rev",
2401 user => $this->{_session}->{user},
2402 }
2403 );
2404}
2405
2406=begin TML
2407
2408---++ ObjectMethod replaceMostRecentRevision( %opts )
2409Replace the most recent revision with whatever is in the memory copy.
2410Only works on topics.
2411
2412%opts may include:
2413 * =forcedate= - try and re-use the date of the original check
2414 * =user= - cUID of the user doing the action
2415
2416=cut
2417
2418sub replaceMostRecentRevision {
2419 my $this = shift;
2420 my %opts = @_;
2421 _assertIsTopic($this) if DEBUG;
2422
2423 my $cUID = $opts{user} || $this->{_session}->{user};
2424
2425 $this->_atomicLock($cUID);
2426
2427 my $info = $this->getRevisionInfo();
2428
2429 if ( $opts{forcedate} ) {
2430
2431 # We are trying to force the rev to be saved with the same date
2432 # and user as the prior rev. However, exactly the same date may
2433 # cause some revision control systems to barf, so to avoid this we
2434 # add 1 minute to the rev time. Note that this mode of operation
2435 # will normally require sysadmin privilege, as it can result in
2436 # confused rev dates if abused.
2437 $info->{date} += 60;
2438 }
2439 else {
2440
2441 # use defaults (current time, current user)
2442 $info->{date} = time();
2443 $info->{author} = $cUID;
2444 }
2445
2446 # repRev is required so we can tell when a merge is based on something
2447 # that is *not* the original rev where another users' edit started.
2448 $info->{reprev} = $info->{version};
2449 $this->setRevisionInfo(%$info);
2450
2451 try {
2452 $this->{_session}->{store}->repRev( $this, $info->{author}, @_ );
2453 }
2454 finally {
2455 $this->_atomicUnlock($cUID);
2456 $this->fireDependency();
2457 };
2458
2459 # write log entry
2460 require Foswiki::Time;
2461 my @extras = ( $info->{version} );
2462 push( @extras,
2463 Foswiki::Time::formatTime( $info->{date}, '$rcs', 'gmtime' ) );
2464 push( @extras, 'minor' ) if $opts{minor};
2465 push( @extras, 'dontlog' ) if $opts{dontlog};
2466 push( @extras, 'forced' ) if $opts{forcedate};
2467 $this->{_session}->logger->log(
2468 {
2469 level => 'info',
2470 action => 'reprev',
2471 webTopic => $this->getPath(),
2472 extra => join( ', ', @extras ),
2473 user => $cUID,
2474 }
2475 );
2476}
2477
2478=begin TML
2479
2480---++ ObjectMethod getRevisionHistory([$attachment]) -> $iterator
2481
2482Get an iterator over the range of version identifiers (just the identifiers,
2483not the content) starting with the most recent revision.
2484
2485The iterator will be empty ($iterator->hasNext() will be false) if the object
2486does not exist.
2487
2488$attachment is optional.
2489
2490Not valid on webs.
2491
2492=cut
2493
2494
# spent 747µs (16+731) within Foswiki::Meta::getRevisionHistory which was called 2 times, avg 374µs/call: # once (12µs+396µs) by Foswiki::UI::View::view at line 166 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm # once (4µs+335µs) by Foswiki::UI::View::revisionsAround at line 507 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm
sub getRevisionHistory {
249521µs my ( $this, $attachment ) = @_;
2496 _assertIsTopic($this) if DEBUG;
2497
2498# if ((not defined($attachment)) and ($this->{_latestIsLoaded})) {
2499# #why poke around in revision history (slow) if we 'have the latest'
2500# return new Foswiki::Iterator::NumberRangeIterator( $this->{_loadedRev}, 1 );
2501# }
2502
2503214µs2731µs return $this->{_session}->{store}->getRevisionHistory( $this, $attachment );
# spent 731µs making 2 calls to Foswiki::Store::Rcs::Store::getRevisionHistory, avg 366µs/call
2504}
2505
2506=begin TML
2507
2508---++ ObjectMethod getLatestRev[$attachment]) -> $revision
2509
2510Get the revision ID of the latest revision.
2511
2512$attachment is optional.
2513
2514Not valid on webs.
2515
2516Returns an integer revision number > 0 if the object exists.
2517
2518Returns 0 if the object does not exist.
2519
2520=cut
2521
2522sub getLatestRev {
2523 my $this = shift;
2524 my $it = $this->getRevisionHistory(@_);
2525 return 0 unless $it->hasNext();
2526 return $it->next();
2527}
2528
2529=begin TML
2530
2531---++ ObjectMethod latestIsLoaded() -> $boolean
2532Return true if the currently loaded rev is the latest rev. Note that there may have
2533been changes to the meta or text locally in the loaded meta; these changes will be
2534retained.
2535
2536Only valid on topics.
2537
2538=cut
2539
2540
# spent 93.2ms within Foswiki::Meta::latestIsLoaded which was called 70084 times, avg 1µs/call: # 61320 times (82.5ms+0s) by Foswiki::MetaCache::addMeta at line 150 of /var/www/foswikidev/core/lib/Foswiki/MetaCache.pm, avg 1µs/call # 8760 times (10.7ms+0s) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 211 of /var/www/foswikidev/core/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 1µs/call # 3 times (8µs+0s) by Foswiki::Store::Rcs::Store::getVersionInfo at line 353 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 3µs/call # once (3µs+0s) by Foswiki::QUERY at line 34 of /var/www/foswikidev/core/lib/Foswiki/Macros/QUERY.pm
sub latestIsLoaded {
25417008413.9ms my $this = shift;
2542 _assertIsTopic($this) if DEBUG;
254370084259ms return $this->{_latestIsLoaded} if defined $this->{_latestIsLoaded};
254438µs return defined $this->{_loadedRev}
2545 && $this->{_loadedRev} == $this->getLatestRev();
2546}
2547
2548=begin TML
2549
2550---++ ObjectMethod getLoadedRev() -> $integer
2551
2552Get the currently loaded revision. Result will be a revision number, or
2553undef if no revision has been loaded. Only valid on topics.
2554
2555WARNING: some store implementations use the concept of a "working copy" of
2556each topic that may be modified *without* being added to the revision
2557control system. This means that the version number reported for the latest
2558rev may not be the actual latest version.
2559
2560=cut
2561
2562
# spent 138ms within Foswiki::Meta::getLoadedRev which was called 122649 times, avg 1µs/call: # 122640 times (138ms+0s) by Foswiki::MetaCache::addMeta at line 150 of /var/www/foswikidev/core/lib/Foswiki/MetaCache.pm, avg 1µs/call # 3 times (8µs+0s) by Foswiki::META at line 37 of /var/www/foswikidev/core/lib/Foswiki/Macros/META.pm, avg 3µs/call # 3 times (5µs+0s) by Foswiki::REVINFO at line 28 of /var/www/foswikidev/core/lib/Foswiki/Macros/REVINFO.pm, avg 2µs/call # 3 times (4µs+0s) by Foswiki::Store::Rcs::Store::getVersionInfo at line 348 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 1µs/call
sub getLoadedRev {
256312264922.9ms my $this = shift;
2564 _assertIsTopic($this) if DEBUG;
2565122649408ms return $this->{_loadedRev};
2566}
2567
2568=begin TML
2569
2570---++ ObjectMethod setLoadStatus($rev, $isLatest)
2571
2572Used by the Store implementation to set the load status
2573when a topic is read. Must be called by implementations of
2574=Foswiki::Store::readTopic=. Do not use for anything else!
2575
2576=cut
2577
2578
# spent 106ms within Foswiki::Meta::setLoadStatus which was called 52654 times, avg 2µs/call: # 26327 times (57.6ms+0s) by Foswiki::Store::Rcs::Store::readTopic at line 187 of /var/www/foswikidev/core/lib/Foswiki/Store/Rcs/Store.pm, avg 2µs/call # 26327 times (48.6ms+0s) by Foswiki::Meta::loadVersion at line 1155, avg 2µs/call
sub setLoadStatus {
25795265411.9ms my $this = shift;
258052654238ms ( $this->{_loadedRev}, $this->{_latestIsLoaded} ) = @_;
2581}
2582
2583=begin TML
2584
2585---++ ObjectMethod removeFromStore( $attachment )
2586 * =$attachment= - optional, provide to delete an attachment
2587
2588Use with great care! Removes all trace of the given web, topic
2589or attachment from the store, possibly including all its history.
2590
2591Also does not ensure consistency of the store
2592(for eg, if you delete an attachment, it does not update the in-topic META)
2593
2594=cut
2595
2596sub removeFromStore {
2597 my ( $this, $attachment ) = @_;
2598 my $store = $this->{_session}->{store};
2599 ASSERT( $this->{_web}, 'this is not a removable object' ) if DEBUG;
2600
2601 if ( !$store->webExists( $this->{_web} ) ) {
2602 throw Error::Simple( 'No such web ' . $this->{_web} );
2603 }
2604 if ( $this->{_topic}
2605 && !$store->topicExists( $this->{_web}, $this->{_topic} ) )
2606 {
2607 throw Error::Simple(
2608 'No such topic ' . $this->{_web} . '.' . $this->{_topic} );
2609 }
2610
2611 if ( $attachment && !$this->hasAttachment($attachment) ) {
2612 ASSERT( $this->{topic}, 'this is not a removable object' ) if DEBUG;
2613 throw Error::Simple( 'No such attachment '
2614 . $this->{_web} . '.'
2615 . $this->{_topic} . '.'
2616 . $attachment );
2617 }
2618 $store->remove( $this->{_session}->{user}, $this, $attachment );
2619 $this->{_session}->{store}->recordChange(
2620 verb => 'remove',
2621 cuid => $this->{_session}->{user},
2622
2623 # revision = -1 when removing webs
2624 revision => $this->{_loadedRev} || -1,
2625 path => $this->getPath(),
2626 attachment => $attachment
2627 );
2628}
2629
2630=begin TML
2631
2632---++ ObjectMethod getDifferences( $rev2, $contextLines ) -> \@diffArray
2633
2634Get the differences between the rev loaded into this object, and another
2635rev of the same topic. Return reference to an array of differences.
2636 * =$rev2= - the other revision to diff against
2637 * =$contextLines= - number of lines of context required
2638
2639Each difference is of the form [ $type, $right, $left ] where
2640| *type* | *Means* |
2641| =+= | Added |
2642| =-= | Deleted |
2643| =c= | Changed |
2644| =u= | Unchanged |
2645| =l= | Line Number |
2646
2647=cut
2648
2649sub getDifferences {
2650 my ( $this, $rev2, $contextLines ) = @_;
2651 _assertIsTopic($this) if DEBUG;
2652 return $this->{_session}->{store}
2653 ->getRevisionDiff( $this, $rev2, $contextLines );
2654}
2655
2656=begin TML
2657
2658---++ ObjectMethod getRevisionAtTime( $time ) -> $rev
2659 * =$time= - time (in epoch secs) for the rev
2660
2661Get the revision number for a topic at a specific time.
2662Returns a single-digit rev number or 0 if it couldn't be determined
2663(either because the topic isn't that old, or there was a problem)
2664
2665=cut
2666
2667sub getRevisionAtTime {
2668 my ( $this, $time ) = @_;
2669 _assertIsTopic($this) if DEBUG;
2670 return $this->{_session}->{store}->getRevisionAtTime( $this, $time );
2671}
2672
2673=begin TML
2674
2675---++ ObjectMethod setLease( $length )
2676
2677Take out an lease on the given topic for this user for $length seconds.
2678
2679See =getLease= for more details about Leases.
2680
2681=cut
2682
2683sub setLease {
2684 my ( $this, $length ) = @_;
2685 _assertIsTopic($this) if DEBUG;
2686 my $t = time();
2687 my $lease = {
2688 user => $this->{_session}->{user},
2689 expires => $t + $length,
2690 taken => $t
2691 };
2692 return $this->{_session}->{store}->setLease( $this, $lease );
2693}
2694
2695=begin TML
2696
2697---++ ObjectMethod getLease() -> $lease
2698
2699If there is an lease on the topic, return the lease, otherwise undef.
2700A lease is a block of meta-information about a topic that can be
2701recovered (this is a hash containing =user=, =taken= and =expires=).
2702Leases are taken out when a topic is edited. Only one lease
2703can be active on a topic at a time. Leases are used to warn if
2704another user is already editing a topic.
2705
2706=cut
2707
2708sub getLease {
2709 my $this = shift;
2710 _assertIsTopic($this) if DEBUG;
2711 return $this->{_session}->{store}->getLease($this);
2712}
2713
2714=begin TML
2715
2716---++ ObjectMethod clearLease()
2717
2718Cancel the current lease.
2719
2720See =getLease= for more details about Leases.
2721
2722=cut
2723
2724sub clearLease {
2725 my $this = shift;
2726 _assertIsTopic($this) if DEBUG;
2727 $this->{_session}->{store}->setLease($this);
2728}
2729
2730=begin TML
2731
2732---++ ObjectMethod onTick($time)
2733
2734Method invoked at regular intervals, usually by a cron job. The job of
2735this method is to prod the store into cleaning up expired leases, and
2736any other admin job that needs doing at regular intervals.
2737
2738=cut
2739
2740sub onTick {
2741 my ( $this, $time ) = @_;
2742
2743 if ( !$this->{_topic} ) {
2744 my $it = $this->eachWeb();
2745 while ( $it->hasNext() ) {
2746 my $web = $it->next();
2747 $web = $this->getPath() . "/$web" if $this->getPath();
2748 my $m = $this->new( $this->{_session}, $web );
2749 $m->onTick($time);
2750 }
2751 if ( $this->{_web} ) {
2752 $it = $this->eachTopic();
2753 while ( $it->hasNext() ) {
2754 my $topic = $it->next();
2755 my $topicObject =
2756 $this->new( $this->{_session}, $this->getPath(), $topic );
2757 $topicObject->onTick($time);
2758 }
2759 }
2760
2761 # Clean up spurious leases that may have been left behind
2762 # during cancelled topic creation
2763 $this->{_session}->{store}->removeSpuriousLeases( $this->getPath() )
2764 if $this->getPath();
2765 }
2766 else {
2767 my $lease = $this->getLease();
2768 if ( $lease && $lease->{expires} < $time ) {
2769 $this->clearLease();
2770 }
2771 }
2772}
2773
2774############# ATTACHMENTS ON TOPICS #############
2775
2776=begin TML
2777
2778---++ *Deprecated* ObjectMethod getAttachmentRevisionInfo($attachment, $rev) -> \%info
2779 * =$attachment= - attachment name
2780 * =$rev= - optional integer attachment revision number
2781Get revision info for an attachment. Only valid on topics.
2782
2783$info will contain at least: date, author, version, comment
2784
2785*Deprecated* 2014-11-03 use getRevisionInfo instead.
2786
2787=cut
2788
2789sub getAttachmentRevisionInfo {
2790 my ( $this, $attachment, $fromrev ) = @_;
2791 _assertIsTopic($this) if DEBUG;
2792
2793 return $this->{_session}->{store}
2794 ->getVersionInfo( $this, $fromrev, $attachment );
2795}
2796
2797=begin TML
2798
2799---++ ObjectMethod attach ( %opts )
2800
2801 * =%opts= may include:
2802 * =name= - Name of the attachment - required
2803 * =dontlog= - don't add to statistics
2804 * =comment= - comment for save
2805 * =hide= - if the attachment is to be hidden in normal topic view
2806 * =stream= - Stream of file to upload. Uses =file= if not set.
2807 * =file= - Name of a *server* file to use for the attachment
2808 data. This should be passed if it is known, as it may be used
2809 to optimise handler calls.
2810 * =filepath= - Optional. Client path to file.
2811 * =filesize= - Optional. Size of uploaded data.
2812 * =filedate= - Optional. Date of file.
2813 * =author= - Optional. cUID of author of change. Defaults to current.
2814 * =notopicchange= - Optional. if the topic is *not* to be modified.
2815 This may result in incorrect meta-data stored in the topic, so must
2816 be used with care. Only has a meaning if the store implementation
2817 stores meta-data in topics.
2818 * =nohandlers= - *do not* call plugin handlers
2819
2820Saves a new revision of the attachment, invoking plugin handlers as
2821appropriate. This method automatically updates the loaded rev of $this
2822to the latest topic revision.
2823
2824If neither of =stream= or =file= are set, this is a properties-only save.
2825
2826Throws an exception on error.
2827
2828=cut
2829
2830# SMELL: arguably should only be permitted if the loaded rev of the object is the same as the
2831# latest rev.
2832
2833sub attach {
2834 my $this = shift;
2835 my %opts = @_;
2836 my $action;
2837 my $plugins = $this->{_session}->{plugins};
2838 _assertIsAttachment( $this, $opts{name} ) if DEBUG;
2839
2840 # make sure we don't save a half-loaded topic stub...
2841 # which indeed - SMELL - is possible
2842 $this->loadVersion() unless $this->latestIsLoaded();
2843
2844 #ASSERT( $this->latestIsLoaded(), $this->getPath() ) if DEBUG;
2845 #ASSERT( $this->{_loadedRev}, $this->getPath() ) if DEBUG;
2846
2847 if ( $opts{file} && !$opts{stream} ) {
2848
2849 # no stream given, but a file was given; open it.
2850 open( $opts{stream}, '<', $opts{file} )
2851 || throw Error::Simple( 'Could not open ' . $opts{file} );
2852 binmode( $opts{stream} )
2853 || throw Error::Simple( $opts{file} . ' binmode failed: ' . $! );
2854 }
2855
2856 my $attrs;
2857 if ( $opts{stream} ) {
2858 $action = 'upload';
2859
2860 $attrs = {
2861 name => $opts{name},
2862 attachment => $opts{name},
2863 stream => $opts{stream},
2864 user => $opts{author} || $this->{_session}->{user}, # cUID
2865 comment => defined $opts{comment} ? $opts{comment} : '',
2866 };
2867
2868 my $handlers_called = 0;
2869
2870 if ( !$opts{nohandlers}
2871 && $plugins->haveHandlerFor('beforeAttachmentSaveHandler') )
2872 {
2873
2874 # *Deprecated* handler.
2875
2876 # The handler may have been called as a result of an upload,
2877 # in which case the data is already in a file in the CGI cache,
2878 # and the stream is valid, or it may be been arrived at via a
2879 # call to Func::saveAttachment, in which case it's possible that
2880 # the stream isn't open but we have a tmpFilename instead.
2881 #
2882 $attrs->{tmpFilename} = $opts{file};
2883
2884 if ( !defined( $attrs->{tmpFilename} ) ) {
2885
2886 # CGI (or the caller) did not provide a temporary file
2887
2888 # Stream the data to a temporary file, so it can be passed
2889 # to the handler.
2890
2891 require File::Temp;
2892
2893 my $fh = new File::Temp();
2894 binmode($fh);
2895
2896 # transfer 512KB blocks
2897 my $transfer;
2898 my $r;
2899 while ( $r = sysread( $opts{stream}, $transfer, 0x80000 ) ) {
2900 if ( !defined $r ) {
2901 next if ( $! == Errno::EINTR );
2902 die "system read error: $!\n";
2903 }
2904 my $offset = 0;
2905 while ($r) {
2906 my $w = syswrite( $fh, $transfer, $r, $offset );
2907 die "system write error: $!\n" unless ( defined $w );
2908 $offset += $w;
2909 $r -= $w;
2910 }
2911 }
2912 select( ( select($fh), $| = 1 )[0] );
2913
2914 # $fh->seek only in File::Temp 0.17 and later
2915 seek( $fh, 0, 0 ) or die "Can't seek temp: $!\n";
2916 $opts{stream} = $fh;
2917 $attrs->{tmpFilename} = $fh->filename();
2918 }
2919
2920 $plugins->dispatch( 'beforeAttachmentSaveHandler', $attrs,
2921 $this->{_topic}, $this->{_web} );
2922
2923 # Have to assume it's changed, even if it hasn't.
2924 open( $attrs->{stream}, '<', $attrs->{tmpFilename} )
2925 || die "Internal error: $!";
2926 binmode( $attrs->{stream} );
2927 $opts{stream} = $attrs->{stream};
2928
2929 delete $attrs->{tmpFilename};
2930
2931 $handlers_called = 1;
2932 }
2933
2934 if ( !$opts{nohandlers}
2935 && $plugins->haveHandlerFor('beforeUploadHandler') )
2936 {
2937
2938 # Check the stream is seekable
2939 ASSERT(
2940 seek( $attrs->{stream}, 0, 1 ),
2941 'Stream for attachment is not seekable'
2942 ) if DEBUG;
2943
2944 $plugins->dispatch( 'beforeUploadHandler', $attrs, $this );
2945 $opts{stream} = $attrs->{stream};
2946 seek( $opts{stream}, 0, 0 ); # seek to beginning
2947 binmode( $opts{stream} );
2948
2949 $handlers_called = 1;
2950 }
2951
2952 if ($handlers_called) {
2953
2954 # Force reload of the latest version
2955 # Note that latestIsLoaded may still be false if the
2956 # topic doesn't exist yet
2957 $this->unload();
2958 $this->loadVersion();
2959 }
2960
2961 $opts{author} ||= $this->{_session}->{user};
2962
2963 my $error;
2964 try {
2965 my $arev =
2966 $this->{_session}->{store}
2967 ->saveAttachment( $this, $opts{name}, $opts{stream},
2968 $opts{author}, \%opts );
2969
2970 $attrs->{version} = $arev;
2971 $attrs->{path} = $opts{filepath} if defined $opts{filepath};
2972 $attrs->{size} = $opts{filesize} if defined $opts{filesize};
2973 $attrs->{date} = defined $opts{filedate} ? $opts{filedate} : time();
2974
2975 # Note that there will be two events; the attachment save,
2976 # followed by the topic update
2977 $this->{_session}->{store}->recordChange(
2978 verb => $arev > 1 ? 'update' : 'insert',
2979 cuid => $opts{author},
2980 revision => $this->{_loadedRev} || 1,
2981 path => $this->getPath(),
2982 attachment => $opts{name},
2983 comment => "add $arev"
2984 );
2985
2986 if ( !$opts{nohandlers}
2987 && $plugins->haveHandlerFor('afterAttachmentSaveHandler') )
2988 {
2989
2990 # *Deprecated* handler
2991 $plugins->dispatch( 'afterAttachmentSaveHandler', $attrs,
2992 $this->{_topic}, $this->{_web} );
2993 }
2994 }
2995 finally {
2996 $this->fireDependency();
2997 };
2998 }
2999 else {
3000
3001 # Property change
3002 $action = 'save';
3003 $attrs = $this->get( 'FILEATTACHMENT', $opts{name} );
3004 $attrs->{name} = $opts{name};
3005 $attrs->{comment} = $opts{comment} if ( defined( $opts{comment} ) );
3006 }
3007 $attrs->{attr} = ( $opts{hide} ) ? 'h' : '';
3008 delete $attrs->{stream};
3009 delete $attrs->{tmpFilename};
3010 $this->putKeyed( 'FILEATTACHMENT', $attrs );
3011
3012 if ( $opts{createlink} ) {
3013 my $text = $this->text();
3014 $text = '' unless defined $text;
3015 $text .=
3016 $this->{_session}->attach->getAttachmentLink( $this, $opts{name} );
3017 $this->text($text);
3018 }
3019
3020 $this->saveAs() unless $opts{notopicchange};
3021
3022 my @extras = ( $opts{name} );
3023 push( @extras, 'dontlog' ) if $opts{dontlog}; # no statistics
3024 $this->{_session}->logger->log(
3025 {
3026 level => 'info',
3027 action => $action,
3028 webTopic => $this->{_web} . '.' . $this->{_topic},
3029 extra => join( ', ', @extras ),
3030 user => $this->{_session}->{user},
3031 }
3032 );
3033
3034 if ( !$opts{nohandlers} && $plugins->haveHandlerFor('afterUploadHandler') )
3035 {
3036 $plugins->dispatch( 'afterUploadHandler', $attrs, $this );
3037 }
3038}
3039
3040=begin TML
3041
3042---++ ObjectMethod hasAttachment( $name ) -> $boolean
3043Test if the named attachment exists. Only valid on topics. The attachment
3044must exist in the store (it is not sufficient for it to be referenced
3045in the object only)
3046
3047=cut
3048
3049
# spent 116µs (23+93) within Foswiki::Meta::hasAttachment which was called: # once (23µs+93µs) by Foswiki::_lookupIcon at line 53 of /var/www/foswikidev/core/lib/Foswiki/Macros/ICON.pm
sub hasAttachment {
305011µs my ( $this, $name ) = @_;
3051 _assertIsAttachment( $this, $name ) if DEBUG;
3052118µs193µs return $this->{_session}->{store}->attachmentExists( $this, $name );
# spent 93µs making 1 call to Foswiki::Store::Rcs::Store::attachmentExists
3053}
3054
3055=begin TML
3056
3057---++ ObjectMethod testAttachment( $name, $test ) -> $value
3058
3059Performs a type test on the given attachment file.
3060 * =$name= - name of the attachment to test e.g =lolcat.gif=
3061 * =$test= - the test to perform e.g. ='r'=
3062
3063The return value is the value that would be returned by the standard
3064perl file operations, as indicated by $type
3065
3066 * r File is readable by current user (tests Foswiki VIEW permission)
3067 * w File is writable by current user (tests Foswiki CHANGE permission)
3068 * e File exists.
3069 * z File has zero size.
3070 * s File has nonzero size (returns size).
3071 * T File is an ASCII text file (heuristic guess).
3072 * B File is a "binary" file (opposite of T).
3073 * M Last modification time (epoch seconds).
3074 * A Last access time (epoch seconds).
3075
3076Note that all these types should behave as the equivalent standard perl
3077operator behaves, except M and A which are independent of the script start
3078time (see perldoc -f -X for more information)
3079
3080Other standard Perl file tests may also be supported on some store
3081implementations, but cannot be relied on.
3082
3083Errors will be signalled by an Error::Simple exception.
3084
3085=cut
3086
3087sub testAttachment {
3088 my ( $this, $attachment, $test ) = @_;
3089 _assertIsAttachment( $this, $attachment ) if DEBUG;
3090
3091 $this->addDependency();
3092
3093 $test =~ m/(\w)/;
3094 $test = $1;
3095 if ( $test eq 'r' ) {
3096 return $this->haveAccess('VIEW');
3097 }
3098 elsif ( $test eq 'w' ) {
3099 return $this->haveAccess('CHANGE');
3100 }
3101
3102 return
3103 return $this->{_session}->{store}
3104 ->testAttachment( $this, $attachment, $test );
3105}
3106
3107=begin TML
3108
3109---+++ openAttachment($attachment, $mode, %opts) -> $fh
3110 * =$attachment= - the attachment
3111 * =$mode= - mode to open the attachment in
3112Opens a stream onto the attachment. This method is primarily to
3113support virtual file systems, and as such access controls are *not*
3114checked, plugin handlers are *not* called, and it does *not* update the
3115meta-data in the topicObject.
3116
3117=$mode= can be '&lt;', '&gt;' or '&gt;&gt;' for read, write, and append
3118respectively.
3119
3120=%opts= can take different settings depending on =$mode=.
3121 * =$mode='&lt;'=
3122 * =version= - revision of the object to open e.g. =version => 6=
3123 * =$mode='&gt;'= or ='&gt;&gt;'
3124 * no options
3125Errors will be signalled by an =Error= exception.
3126
3127See also =attach= if this function is too basic for you.
3128
3129=cut
3130
3131sub openAttachment {
3132 my ( $this, $attachment, $mode, @opts ) = @_;
3133 _assertIsAttachment( $this, $attachment ) if DEBUG;
3134 ASSERT($attachment) if DEBUG;
3135
3136 return $this->{_session}->{store}
3137 ->openAttachment( $this, $attachment, $mode, @opts );
3138
3139}
3140
3141=begin TML
3142
3143---++ ObjectMethod moveAttachment( $name, $to, %opts ) -> $data
3144Move the named attachment to the topic indicates by $to.
3145=%opts= may include:
3146 * =new_name= - new name for the attachment
3147 * =user= - cUID of user doing the moving
3148
3149=cut
3150
3151sub moveAttachment {
3152 my $this = shift;
3153 my $name = shift;
3154 my $to = shift;
3155 my %opts = @_;
3156 my $cUID = $opts{user} || $this->{_session}->{user};
3157 _assertIsAttachment( $this, $name ) if DEBUG;
3158 _assertIsTopic($to) if DEBUG;
3159
3160 my $newName = $opts{new_name} || $name;
3161
3162 # Make sure we have latest revs
3163 $this = $this->load() unless $this->latestIsLoaded();
3164
3165 $this->_atomicLock($cUID);
3166 $to->_atomicLock($cUID);
3167
3168 try {
3169 $this->{_session}->{store}
3170 ->moveAttachment( $this, $name, $to, $newName, $cUID );
3171
3172 # Modify the cache of the old topic
3173 my $fileAttachment = $this->get( 'FILEATTACHMENT', $name );
3174 $this->remove( 'FILEATTACHMENT', $name );
3175 $this->saveAs(
3176 dontlog => 1, # no statistics
3177 comment => 'lost ' . $name
3178 );
3179
3180 # Add file attachment to new topic
3181 $fileAttachment->{name} = $newName;
3182 $fileAttachment->{movefrom} = $this->getPath() . '.' . $name;
3183 $fileAttachment->{moveby} =
3184 $this->{_session}->{users}->getLoginName($cUID);
3185 $fileAttachment->{movedto} = $to->getPath() . '.' . $newName;
3186 $fileAttachment->{movedwhen} = time();
3187 $to->loadVersion();
3188 $to->putKeyed( 'FILEATTACHMENT', $fileAttachment );
3189
3190 if ( $this->getPath() eq $to->getPath() ) {
3191 $to->remove( 'FILEATTACHMENT', $name );
3192 }
3193
3194 $to->saveAs(
3195 dontlog => 1, # no statistics
3196 comment => 'gained' . $newName
3197 );
3198
3199 $this->{_session}->{store}->recordChange(
3200 cuid => $cUID,
3201 revision => $to->{_loadedRev},
3202 verb => 'update',
3203 oldpath => $this->getPath(),
3204 oldattachment => $name,
3205 path => $to->getPath(),
3206 attachment => $newName
3207 );
3208
3209 }
3210 finally {
3211 $to->_atomicUnlock($cUID);
3212 $this->_atomicUnlock($cUID);
3213 $this->fireDependency();
3214 $to->fireDependency();
3215 };
3216
3217 # alert plugins of attachment move
3218 $this->{_session}->{plugins}
3219 ->dispatch( 'afterRenameHandler', $this->{_web}, $this->{_topic}, $name,
3220 $to->{_web}, $to->{_topic}, $newName );
3221
3222 $this->{_session}->logger->log(
3223 {
3224 level => 'info',
3225 action => 'move',
3226 webTopic => $this->getPath() . '.' . $name,
3227 extra => ' moved to ' . $to->getPath() . '.' . $newName,
3228 user => $cUID,
3229 }
3230 );
3231}
3232
3233=begin TML
3234
3235---++ ObjectMethod copyAttachment( $name, $to, %opts ) -> $data
3236Copy the named attachment to the topic indicates by $to.
3237=%opts= may include:
3238 * =new_name= - new name for the attachment
3239 * =user= - cUID of user doing the moving
3240
3241=cut
3242
3243sub copyAttachment {
3244 my $this = shift;
3245 my $name = shift;
3246 my $to = shift;
3247 my %opts = @_;
3248 my $cUID = $opts{user} || $this->{_session}->{user};
3249 _assertIsAttachment( $this, $name ) if DEBUG;
3250 _assertIsTopic($to) if DEBUG;
3251
3252 my $newName = $opts{new_name} || $name;
3253
3254 # Make sure we have latest revs
3255 my $from;
3256 if ( $this->latestIsLoaded() ) {
3257 $from = $this;
3258 }
3259 else {
3260 $from = $this->load();
3261 }
3262
3263 $from->_atomicLock($cUID);
3264 $to->_atomicLock($cUID);
3265
3266 try {
3267 $from->{_session}->{store}
3268 ->copyAttachment( $from, $name, $to, $newName, $cUID );
3269
3270 # Add file attachment to new topic by copying the old one
3271 my $fileAttachment = { %{ $from->get( 'FILEATTACHMENT', $name ) } };
3272 $fileAttachment->{name} = $newName;
3273
3274 $to->loadVersion() unless $to->latestIsLoaded();
3275 $to->putKeyed( 'FILEATTACHMENT', $fileAttachment );
3276
3277 if ( $from->getPath() eq $to->getPath() ) {
3278 $to->remove( 'FILEATTACHMENT', $name );
3279 }
3280
3281 $to->saveAs(
3282 author => $cUID,
3283 dontlog => 1, # no statistics
3284 comment => 'gained' . $newName
3285 );
3286 $this->{_session}->{store}->recordChange(
3287 verb => 'copy',
3288 cuid => $cUID,
3289 revision => $to->{_loadedRev},
3290 oldpath => $from->getPath(),
3291 oldattachment => $name,
3292 path => $to->getPath(),
3293 attachment => $newName
3294 );
3295
3296 }
3297 finally {
3298 $to->_atomicUnlock($cUID);
3299 $from->_atomicUnlock($cUID);
3300 $from->fireDependency();
3301 $to->fireDependency();
3302 };
3303
3304 # alert plugins of attachment move
3305 # SMELL: no defined handler for attachment copies
3306 # $this->{_session}->{plugins}
3307 # ->dispatch( 'afterCopyHandler', $this->{_web}, $this->{_topic}, $name,
3308 # $to->{_web}, $to->{_topic}, $newName );
3309
3310 $this->{_session}->logger->log(
3311 {
3312 level => 'info',
3313 action => 'copy',
3314 webTopic => $this->getPath() . '.' . $name,
3315 extra => ' copied to ' . $to->getPath() . '.' . $newName,
3316 user => $cUID,
3317 }
3318 );
3319}
3320
3321=begin TML
3322
3323---++ ObjectMethod expandNewTopic()
3324Expand only that subset of Foswiki variables that are
3325expanded during topic creation, in the body text and
3326PREFERENCE meta only.
3327
3328The expansion is in-place in the object data.
3329
3330Only valid on topics.
3331
3332=cut
3333
3334sub expandNewTopic {
3335 my ($this) = @_;
3336 _assertIsTopic($this) if DEBUG;
3337 $this->{_session}->expandMacrosOnTopicCreation($this);
3338}
3339
3340=begin TML
3341
3342---++ ObjectMethod expandMacros( $text ) -> $text
3343Expand only all Foswiki variables that are
3344expanded during topic view. Returns the expanded text.
3345Only valid on topics.
3346
3347=cut
3348
3349
# spent 137s (408µs+137) within Foswiki::Meta::expandMacros which was called 100 times, avg 1.37s/call: # 91 times (349µs+56µs) by Foswiki::Func::expandCommonVariables at line 2658 of /var/www/foswikidev/core/lib/Foswiki/Func.pm, avg 4µs/call # 4 times (27µs+-27µs) by Foswiki::inlineAlert at line 2624 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 0s/call # 3 times (24µs+137s) by Foswiki::UI::View::_prepare at line 479 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm, avg 45.7s/call # 2 times (9µs+10.8ms) by Foswiki::_renderZone at line 3872 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 5.40ms/call
sub expandMacros {
335010058µs my ( $this, $text ) = @_;
3351 _assertIsTopic($this) if DEBUG;
3352
3353100315µs100137s return $this->{_session}->expandMacros( $text, $this );
# spent 137s making 100 calls to Foswiki::expandMacros, avg 1.37s/call, recursion: max depth 2, sum of overlapping time 62.2ms
3354}
3355
3356=begin TML
3357
3358---++ ObjectMethod renderTML( $text ) -> $text
3359Render all TML constructs in the text into HTML. Returns the rendered text.
3360Only valid on topics.
3361
3362=cut
3363
3364
# spent 50.2ms (52µs+50.1) within Foswiki::Meta::renderTML which was called 5 times, avg 10.0ms/call: # 3 times (39µs+44.2ms) by Foswiki::UI::View::_prepare at line 480 of /var/www/foswikidev/core/lib/Foswiki/UI/View.pm, avg 14.8ms/call # 2 times (13µs+5.91ms) by Foswiki::_renderZone at line 3873 of /var/www/foswikidev/core/lib/Foswiki.pm, avg 2.96ms/call
sub renderTML {
3365510µs my ( $this, $text ) = @_;
3366 _assertIsTopic($this) if DEBUG;
3367537µs1050.1ms return $this->{_session}->renderer->getRenderedVersion( $text, $this );
# spent 50.1ms making 5 calls to Foswiki::Render::getRenderedVersion, avg 10.0ms/call # spent 12µs making 5 calls to Foswiki::renderer, avg 2µs/call
3368}
3369
3370=begin TML
3371
3372---++ ObjectMethod summariseText( $flags [, $text, \%searchOptions] ) -> $tml
3373
3374Makes a plain text summary of the topic text by simply trimming a bit
3375off the top. Truncates to $TMTRUNC chars or, if a number is specified
3376in $flags, to that length.
3377
3378If $text is defined, use it in place of the topic text.
3379
3380The =\%searchOptions= hash may contain the following options:
3381 * =type= - search type: keyword, literal, query
3382 * =casesensitive= - false to ignore case (default true)
3383 * =wordboundaries= - if type is 'keyword'
3384 * =tokens= - array ref of search tokens
3385
3386TODO: should this really be in Meta? it seems like a rendering issue to me.
3387
3388warning: this will produce text that contains html entities - including quotes
3389use =$summary = Foswiki::entityEncode($summary);= to diffuse them
3390
3391
3392=cut
3393
3394sub summariseText {
3395 my ( $this, $flags, $text, $searchOptions ) = @_;
3396 _assertIsTopic($this) if DEBUG;
3397
3398 $flags ||= '';
3399
3400 $text = $this->text() unless defined $text;
3401 $text = '' unless defined $text;
3402
3403 my $plainText =
3404 $this->session->renderer->TML2PlainText( $text, $this, $flags );
3405 $plainText =~ s/\n+/ /g;
3406
3407 # limit to n chars
3408 my $limit = $flags || '';
3409 unless ( $limit =~ s/^.*?([0-9]+).*$/$1/ ) {
3410 $limit = $SUMMARY_TMLTRUNC;
3411 }
3412 $limit = $SUMMARY_MINTRUNC if ( $limit < $SUMMARY_MINTRUNC );
3413
3414 if ( $flags =~ m/searchcontext/ ) {
3415 return $this->_summariseTextWithSearchContext( $plainText, $limit,
3416 $searchOptions );
3417 }
3418 else {
3419 return $this->_summariseTextSimple( $plainText, $limit );
3420 }
3421}
3422
3423=begin TML
3424
3425---++ ObjectMethod _summariseTextSimple( $text, $limit ) -> $tml
3426
3427Makes a plain text summary of the topic text by simply trimming a bit
3428off the top. Truncates to $TMTRUNC chars or, if a number is specified
3429in $flags, to that length.
3430
3431TODO: should this really be in Meta? it seems like a rendering issue to me.
3432
3433=cut
3434
3435sub _summariseTextSimple {
3436 my ( $this, $text, $limit ) = @_;
3437 _assertIsTopic($this) if DEBUG;
3438
3439 $text =~ s/^(.{$limit}).*$/$1.../s;
3440
3441 return $this->_makeSummaryTextSafe($text);
3442}
3443
3444sub _makeSummaryTextSafe {
3445 my ( $this, $text ) = @_;
3446
3447 my $session = $this->session();
3448 my $renderer = $session->renderer();
3449
3450 # We do not want the summary to contain any $variable that formatted
3451 # searches can interpret to anything (Item3489).
3452 # Especially new lines (Item2496)
3453 # To not waste performance we simply replace $ by $<nop>
3454 $text =~ s/\$/\$<nop>/g;
3455
3456 # Escape Interwiki links and other side effects introduced by
3457 # plugins later in the rendering pipeline (Item4748)
3458 $text =~ s/\:/<nop>\:/g;
3459 $text =~ s/\s+/ /g;
3460
3461 return $this->session->renderer->protectPlainText($text);
3462}
3463
3464=begin TML
3465
3466---++ ObjectMethod _summariseTextWithSearchContext( $text, $limit, $type, $searchOptions ) -> $tml
3467
3468Improves the presentation of summaries for keyword, word and literal searches, by displaying topic content on either side of the search terms wherever they are found in the topic.
3469
3470The =\%searchOptions= hash may contain the following options:
3471 * =type= - search type: keyword, literal, query
3472 * =casesensitive= - false to ignore case (default true)
3473 * =wordboundaries= - if type is 'keyword'
3474 * =tokens= - array ref of search tokens
3475
3476=cut
3477
3478sub _summariseTextWithSearchContext {
3479 my ( $this, $text, $limit, $searchOptions ) = @_;
3480
3481 if ( !$searchOptions->{tokens} ) {
3482 return $this->_summariseTextSimple( $text, $limit );
3483 }
3484
3485 my $type = $searchOptions->{type} || '';
3486 if ( $type ne 'keyword' && $type ne 'literal' && $type ne '' ) {
3487 return $this->_summariseTextSimple( $text, $limit );
3488 }
3489
3490 my $caseSensitive = $searchOptions->{casesensitive} || '';
3491 my $wordBoundaries = $searchOptions->{wordboundaries} || '';
3492
3493#Item12166
3494#NOTE: this is duplicating the F::Search::Node code, and probably the F::Q:: =~ parse
3495#and the SearchAlgo already deals with this issue to some degree (i'm not sure it does unmatched [ etc)
3496
3497 my $tToken;
3498 my @tokens = map {
3499
3500 $tToken = $_; # copy $_ to avoid changing the passed token
3501
3502#we get a crash if the tokem is not a valid regex. - for eg a single lone *
3503#actually need to escape all things that would trash the regex
3504#TODO: this needs to be extracted from here and Forking.pm and pushed into F::Search::Node
3505 $tToken =~ s#([][|/\\\$\^*()+{};@?.{}])#\\$1#g if ( $type ne 'regex' );
3506 $tToken;
3507 } grep { !/^!.*$/ } @{ $searchOptions->{tokens} };
3508 my $keystrs = join( '|', @tokens );
3509
3510 if ( !$keystrs ) {
3511 return $this->_summariseTextSimple( $text, $limit );
3512 }
3513
3514 # we don't have a means currently to set the word window through a parameter
3515 # so we always use the default
3516 my $context = $SUMMARY_DEFAULT_CONTEXT;
3517
3518# break on words with search type 'word' (which is passed as type 'keyword' with $wordBoundaries as true
3519 my $wordBoundaryAnchor =
3520 ( $type eq 'keyword' && $wordBoundaries ) ? '\b' : '';
3521 $keystrs = $caseSensitive ? "($keystrs)" : "((?i:$keystrs))";
3522 my $termsPattern = $wordBoundaryAnchor . $keystrs . $wordBoundaryAnchor;
3523
3524# if $wordBoundaries is false, only break on whole words at start and end, not surrounding the search term; therefore the pattern at start differs from the pattern at the end
3525 my $beforePattern = "(\\b.{0,$context}$wordBoundaryAnchor)";
3526 my $afterPattern = "($wordBoundaryAnchor.{0,$context}\\b)";
3527 my $searchPattern = $beforePattern . $termsPattern . $afterPattern;
3528
3529 my $summary = '';
3530 my $summaryLength = 0;
3531 while ( $summaryLength < $limit && $text =~ m/$searchPattern/gs ) {
3532 my $before = $1 || '';
3533 my $term = $2 || '';
3534 my $after = $3 || '';
3535
3536 $before = $this->_makeSummaryTextSafe($before);
3537 $term = $this->_makeSummaryTextSafe($term);
3538 $after = $this->_makeSummaryTextSafe($after);
3539
3540 $summaryLength += length "$before$term$after";
3541
3542 my $startLoc = $-[0];
3543
3544 # only show ellipsis when not at the start
3545 # and when we don't have any summary text yet
3546 if ( !$summary && $startLoc != 0 ) {
3547 $before = "$SUMMARY_ELLIPSIS $before";
3548 }
3549
3550 my $endLoc = $+[0] || $-[0];
3551 $after = "$after $SUMMARY_ELLIPSIS" if $endLoc != length $text;
3552
3553 $summary .= $before . CGI::em( {}, $term ) . $after . ' ';
3554 }
3555
3556 return $this->_summariseTextSimple( $text, $limit ) if !$summary;
3557
3558 return $summary;
3559}
3560
3561=begin TML
3562
3563---++ ObjectMethod summariseChanges( $orev, $nrev, $tml, $nochecks) -> $text
3564
3565Generate a (max 3 line) summary of the differences between the revs.
3566
3567 * =$orev= - older rev, if not defined will use ($nrev - 1)
3568 * =$nrev= - later rev, if not defined defaults to latest
3569 * =$tml= - if true will generate renderable TML (i.e. HTML with NOPs.
3570 If false will generate a summary suitable for use in plain text
3571 (mail, for example)
3572 * =$nochecks= - if true, access control checks will be suppressed
3573
3574If there is only one rev, a topic summary will be returned.
3575
3576If =$tml= is not set, all HTML will be removed.
3577
3578In non-tml, lines are truncated to 70 characters. Differences are shown using + and - to indicate added and removed text.
3579
3580=cut
3581
3582sub summariseChanges {
3583 my ( $this, $orev, $nrev, $tml, $nochecks ) = @_;
3584 my $summary = '';
3585 my $session = $this->session();
3586 my $renderer = $session->renderer();
3587
3588 _assertIsTopic($this) if DEBUG;
3589 $nrev = $this->getLatestRev() unless $nrev;
3590
3591 ASSERT( $nrev =~ m/^\s*\d+\s*/ ) if DEBUG; # looks like a number
3592
3593 $orev = $nrev - 1 unless defined($orev);
3594
3595 ASSERT( $orev =~ m/^\s*\d+\s*/ ) if DEBUG; # looks like a number
3596 ASSERT( $orev >= 0 ) if DEBUG;
3597 ASSERT( $nrev >= $orev ) if DEBUG;
3598
3599 unless ( defined $this->{_loadedRev} && $this->{_loadedRev} eq $nrev ) {
3600 $this = $this->load($nrev);
3601 }
3602
3603 my $ntext = '';
3604 if ( $nochecks || $this->haveAccess('VIEW') ) {
3605
3606 # Only get the text if we have access to nrev
3607 $ntext = $this->text();
3608 }
3609
3610 return '' if ( $orev == $nrev ); # same rev, no differences
3611
3612 my $nstring = $this->stringify();
3613 $nstring =~ s/^%META:TOPICINFO\{.*?}%//ms;
3614
3615 #print "SSSSSS nstring\n($nstring)\nSSSSSS\n\n";
3616
3617 $ntext = $renderer->TML2PlainText( $nstring, $this, 'showvar showmeta' );
3618
3619 #print "SSSSSS ntext\n($ntext)\nSSSSSS\n\n";
3620
3621 my $oldTopicObject =
3622 Foswiki::Meta->load( $session, $this->web, $this->topic, $orev );
3623 unless ( $nochecks || $oldTopicObject->haveAccess('VIEW') ) {
3624
3625 # No access to old rev, make a blank topic object
3626 $oldTopicObject =
3627 Foswiki::Meta->new( $session, $this->web, $this->topic, '' );
3628 }
3629
3630 my $ostring = $oldTopicObject->stringify();
3631 $ostring =~ s/^%META:TOPICINFO\{.*?}%$//ms;
3632
3633 #print "SSSSSS ostring\n$ostring\nSSSSSS\n\n";
3634
3635 my $otext =
3636 $renderer->TML2PlainText( $ostring, $oldTopicObject, 'showvar showmeta' );
3637
3638 #print "SSSSSS otext\n($otext)\nSSSSSS\n\n";
3639
3640 require Foswiki::Merge;
3641 my $blocks = Foswiki::Merge::simpleMerge( $otext, $ntext, qr/[\r\n]+/ );
3642
3643 #foreach $b ( @$blocks ) {
3644 # print "BBBB\n($b)\nBBBB\n\n";
3645 # }
3646
3647 # sort through, keeping one line of context either side of a change
3648 my @revised;
3649 my $getnext = 0;
3650 my $prev = '';
3651 my $ellipsis = $tml ? $SUMMARY_ELLIPSIS : '...';
3652 my $trunc = $tml ? $SUMMARY_TMLTRUNC : $CHANGES_SUMMARY_PLAINTRUNC;
3653 while ( scalar(@$blocks) && scalar(@revised) < $CHANGES_SUMMARY_LINECOUNT )
3654 {
3655 my $block = shift(@$blocks);
3656 next unless $block =~ m/\S/;
3657 my $trim = length($block) > $trunc;
3658 $block =~ s/^(.{$trunc}).*$/$1/ if ($trim);
3659 if ( $block =~ m/^[-+]/ ) {
3660 if ($tml) {
3661 $block =~ s/^-(.*)$/CGI::del( {}, $1 )/se;
3662 $block =~ s/^\+(.*)$/CGI::ins( {}, $1 )/se;
3663 }
3664 elsif ( $session->inContext('rss') ) {
3665 $block =~ s/^-/REMOVED: /;
3666 $block =~ s/^\+/INSERTED: /;
3667 }
3668 push( @revised, $prev ) if $prev;
3669 $block .= $ellipsis if $trim;
3670 push( @revised, $block );
3671 $getnext = 1;
3672 $prev = '';
3673 }
3674 else {
3675 if ($getnext) {
3676 $block .= $ellipsis if $trim;
3677 push( @revised, $block );
3678 $getnext = 0;
3679 $prev = '';
3680 }
3681 else {
3682 $prev = $block;
3683 }
3684 }
3685 }
3686 if ($tml) {
3687 $summary = join( CGI::br(), @revised );
3688 }
3689 else {
3690 $summary = join( "\n", @revised );
3691 }
3692
3693 unless ($summary) {
3694 return $this->summariseText( '', $ntext );
3695 }
3696
3697 #print "SUMMARY\n===================\n($summary)\n============\n\n";
3698
3699 if ( !$tml ) {
3700 $summary = $renderer->protectPlainText($summary);
3701 }
3702 return $summary;
3703}
3704
3705=begin TML
3706
3707---++ *Deprecated* ObjectMethod getEmbeddedStoreForm() -> $text
3708
3709Generate the embedded store form of the topic. The embedded store
3710form has meta-data values embedded using %META: lines. The text
3711stored in the meta is taken as the topic text.
3712
3713*Deprecated* 2014-11-13, and will be removed in Foswiki 2.0.
3714It is retained for compatibility only.
3715use =Foswiki::Serialise::serialise($meta, 'Embedded')= instead.
3716
3717=cut
3718
3719sub getEmbeddedStoreForm {
3720 my $this = shift;
3721
3722 _assertIsTopic($this) if DEBUG;
3723
3724 return Foswiki::Serialise::serialise( $this, 'Embedded' );
3725}
3726
3727=begin TML
3728
3729---++ *Deprecated* ObjectMethod setEmbeddedStoreForm( $text )
3730
3731Populate this object with embedded meta-data from $text. This method
3732is a utility provided for use with stores that store data embedded in
3733topic text. Only valid on topics.
3734
3735Note: line endings must be normalised to \n *before* calling this method.
3736
3737*Deprecated* 2014-11-13, and will be removed in Foswiki 2.0.
3738It is retained for compatibility only.
3739use =Foswiki::Serialise::deserialise($text, 'Embedded', $meta)= instead.
3740
3741=cut
3742
3743sub setEmbeddedStoreForm {
3744 my ( $this, $text ) = @_;
3745
3746 _assertIsTopic($this) if DEBUG;
3747 Foswiki::Serialise::deserialise( $text, 'Embedded', $this );
3748}
3749
3750=begin TML
3751
3752---++ StaticMethod isValidEmbedding($macro, \%args) -> $boolean
3753
3754Test that the arguments defined in =\%args= are sufficient to satisfy the
3755requirements of the embeddable meta-data given by =$macro=. For example,
3756=isValidEmbedding('FILEATTACHMENT', $args)= will only succeed if $args contains
3757at least =name=, =date=, =user= and =attr= fields. Note that extra fields are
3758simply ignored (unless they are explicitly excluded).
3759
3760If the macro is not registered for validation, then it will be ignored.
3761
3762If the embedding is not valid, then $Foswiki::Meta::reason is set with a
3763message explaining why.
3764
3765=cut
3766
3767
# spent 6.20s within Foswiki::Meta::isValidEmbedding which was called 927699 times, avg 7µs/call: # 927699 times (6.20s+0s) by Foswiki::Serialise::Embedded::_readMETA at line 187 of /var/www/foswikidev/core/lib/Foswiki/Serialise/Embedded.pm, avg 7µs/call
sub isValidEmbedding {
3768927699369ms my ( $macro, $args ) = @_;
3769
3770927699465ms my $validate = $VALIDATE{$macro};
3771927699148ms return 1 unless $validate; # not validated
3772
3773927695235ms if ( defined $validate->{function} ) {
3774 unless ( &{ $validate->{function} }( $macro, $args ) ) {
3775 $reason = "\%META:$macro validation failed";
3776 return 0;
3777 }
3778
3779 # Fall through to check other constraints
3780 }
3781
378292769585.2ms my %allowed;
3783927695467ms if ( defined $validate->{require} ) {
3784848767965ms map { $allowed{$_} = 1 } @{ $validate->{require} };
3785848767582ms foreach my $p ( @{ $validate->{require} } ) {
37861664776982ms if ( !defined $args->{$p} ) {
3787 $reason = "$p was missing from \%META:$macro";
3788 return 0;
3789 }
3790 }
3791 }
3792
3793927695215ms if ( defined $validate->{allow} ) {
379478928176ms map { $allowed{$_} = 1 } @{ $validate->{allow} };
379578928152ms foreach my $arg ( keys %$args ) {
3796206182130ms if ( !$allowed{$arg} ) {
3797240416µs $reason = "$arg was present in \%META:$macro";
3798240712µs return 0;
3799 }
3800 }
3801 }
3802
38039274553.79s return 1;
3804}
3805
3806=begin TML
3807
3808---++ StaticMethod dataDecode( $encoded ) -> $decoded
3809
3810Decode escapes in a string that was encoded using dataEncode
3811
3812The encoding has to be exported because Foswiki (and plugins) use
3813encoded field data in other places e.g. RDiff, mainly as a shorthand
3814for the properly parsed meta object. Some day we may be able to
3815eliminate that....
3816
3817=cut
3818
3819
# spent 7.39s within Foswiki::Meta::dataDecode which was called 3323098 times, avg 2µs/call: # 3323098 times (7.39s+0s) by Foswiki::Serialise::Embedded::_readKeyValues at line 178 of /var/www/foswikidev/core/lib/Foswiki/Serialise/Embedded.pm, avg 2µs/call
sub dataDecode {
382033230981.49s my $datum = shift;
3821
382233230982.57s $datum =~ s/%([\da-f]{2})/chr(hex($1))/gei;
3823332309812.8s return $datum;
3824}
3825
3826=begin TML
3827
3828---++ ClassMethod type() => $resourcetype
3829
3830(see Foswiki::Address::type)
3831
3832Returns the resource type name.
3833 * webpath, Eg. =Web/SubWeb/=
3834 * topic, Eg. =Web/SubWeb.
3835 * undef, I have no idea whats going on, we're not there yet
3836
3837=cut
3838
3839sub type {
3840 my ($this) = @_;
3841
3842 if ( defined( $this->{_web} ) ) {
3843 if ( defined( $this->{_topic} ) ) {
3844 return 'topic';
3845 }
3846 return 'webpath';
3847 }
3848 return;
3849}
3850
3851116µs1;
3852__END__