← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 19:05:14 2015
Reported on Fri Jul 31 19:08:09 2015

Filename/var/www/foswiki11/lib/Foswiki/Meta.pm
StatementsExecuted 924167 statements in 1.36s
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
859811223ms282msFoswiki::Meta::::_readKeyValuesFoswiki::Meta::_readKeyValues
778822168ms187msFoswiki::Meta::::putKeyedFoswiki::Meta::putKeyed
19042116168ms187msFoswiki::Meta::::getFoswiki::Meta::get
565132512143ms143msFoswiki::Meta::::webFoswiki::Meta::web
859821135ms685msFoswiki::Meta::::_readMETAFoswiki::Meta::_readMETA
4261176.6ms772msFoswiki::Meta::::setEmbeddedStoreFormFoswiki::Meta::setEmbeddedStoreForm
85981163.0ms63.0msFoswiki::Meta::::isValidEmbeddingFoswiki::Meta::isValidEmbedding
309201159.0ms59.0msFoswiki::Meta::::dataDecodeFoswiki::Meta::dataDecode
18945221349.3ms49.3msFoswiki::Meta::::topicFoswiki::Meta::topic
87612248.3ms54.6msFoswiki::Meta::::latestIsLoadedFoswiki::Meta::latestIsLoaded
91896643.2ms9.66sFoswiki::Meta::::textFoswiki::Meta::text
4096631.9ms712msFoswiki::Meta::::haveAccessFoswiki::Meta::haveAccess (recurses: max depth 1, inclusive time 1.58ms)
1104171124.0ms27.9msFoswiki::Meta::::newFoswiki::Meta::new
8241115.8ms17.8msFoswiki::Meta::::putFoswiki::Meta::put
11044113.8ms363msFoswiki::Meta::::_getACLFoswiki::Meta::_getACL
1336777.73ms8.31sFoswiki::Meta::::getPreferenceFoswiki::Meta::getPreference
426317.21ms11.0sFoswiki::Meta::::loadVersionFoswiki::Meta::loadVersion
555323.84ms4.28msFoswiki::Meta::::findFoswiki::Meta::find
321962.85ms1.38sFoswiki::Meta::::loadFoswiki::Meta::load
308222.80ms4.53msFoswiki::Meta::::getRevisionInfoFoswiki::Meta::getRevisionInfo
336112.76ms23.5msFoswiki::Meta::::existsInStoreFoswiki::Meta::existsInStore
1112.56ms4.91msFoswiki::Meta::::renderFormForDisplayFoswiki::Meta::renderFormForDisplay
426112.32ms2.66msFoswiki::Meta::::countFoswiki::Meta::count
808432.10ms2.10msFoswiki::Meta::::getPathFoswiki::Meta::getPath
762211.78ms1.78msFoswiki::Meta::::addDependencyFoswiki::Meta::addDependency
392211.63ms2.18msFoswiki::Meta::::_writeKeyValueFoswiki::Meta::_writeKeyValue
6211.59ms3.77msFoswiki::Meta::::_writeTypesFoswiki::Meta::_writeTypes
229111.20ms4.92msFoswiki::Meta::::getContainerFoswiki::Meta::getContainer
81851870µs870µsFoswiki::Meta::::MONITOR_ACLSFoswiki::Meta::MONITOR_ACLS
111870µs1.24msFoswiki::Meta::::BEGIN@117Foswiki::Meta::BEGIN@117
39211550µs550µsFoswiki::Meta::::dataEncodeFoswiki::Meta::dataEncode
4111442µs330msFoswiki::Meta::::eachTopicFoswiki::Meta::eachTopic
4111360µs48.7sFoswiki::Meta::::queryFoswiki::Meta::query
454496µs96µsFoswiki::Meta::::sessionFoswiki::Meta::session
21190µs3.87msFoswiki::Meta::::getEmbeddedStoreFormFoswiki::Meta::getEmbeddedStoreForm
73386µs59.1sFoswiki::Meta::::expandMacrosFoswiki::Meta::expandMacros
52282µs61.5msFoswiki::Meta::::renderTMLFoswiki::Meta::renderTML
21156µs92.1msFoswiki::Meta::::eachWebFoswiki::Meta::eachWeb
11140µs178µsFoswiki::Meta::::hasAttachmentFoswiki::Meta::hasAttachment
22138µs394msFoswiki::Meta::::getRevisionHistoryFoswiki::Meta::getRevisionHistory
62232µs38µsFoswiki::Meta::::getLoadedRevFoswiki::Meta::getLoadedRev
11114µs29µsFoswiki::Meta::::BEGIN@113Foswiki::Meta::BEGIN@113
11110µs24µsFoswiki::Meta::::BEGIN@116Foswiki::Meta::BEGIN@116
1119µs16µsFoswiki::Meta::::BEGIN@114Foswiki::Meta::BEGIN@114
1118µs136µsFoswiki::Meta::::BEGIN@115Foswiki::Meta::BEGIN@115
1118µs13µsFoswiki::Meta::::getFormNameFoswiki::Meta::getFormName
0000s0sFoswiki::Meta::::__ANON__[:1619]Foswiki::Meta::__ANON__[:1619]
0000s0sFoswiki::Meta::::__ANON__[:1628]Foswiki::Meta::__ANON__[:1628]
0000s0sFoswiki::Meta::::__ANON__[:1676]Foswiki::Meta::__ANON__[:1676]
0000s0sFoswiki::Meta::::__ANON__[:1680]Foswiki::Meta::__ANON__[:1680]
0000s0sFoswiki::Meta::::__ANON__[:1922]Foswiki::Meta::__ANON__[:1922]
0000s0sFoswiki::Meta::::__ANON__[:1925]Foswiki::Meta::__ANON__[:1925]
0000s0sFoswiki::Meta::::__ANON__[:2046]Foswiki::Meta::__ANON__[:2046]
0000s0sFoswiki::Meta::::__ANON__[:2050]Foswiki::Meta::__ANON__[:2050]
0000s0sFoswiki::Meta::::__ANON__[:2193]Foswiki::Meta::__ANON__[:2193]
0000s0sFoswiki::Meta::::__ANON__[:2199]Foswiki::Meta::__ANON__[:2199]
0000s0sFoswiki::Meta::::__ANON__[:2246]Foswiki::Meta::__ANON__[:2246]
0000s0sFoswiki::Meta::::__ANON__[:2249]Foswiki::Meta::__ANON__[:2249]
0000s0sFoswiki::Meta::::__ANON__[:2308]Foswiki::Meta::__ANON__[:2308]
0000s0sFoswiki::Meta::::__ANON__[:2311]Foswiki::Meta::__ANON__[:2311]
0000s0sFoswiki::Meta::::__ANON__[:2754]Foswiki::Meta::__ANON__[:2754]
0000s0sFoswiki::Meta::::__ANON__[:2757]Foswiki::Meta::__ANON__[:2757]
0000s0sFoswiki::Meta::::__ANON__[:2968]Foswiki::Meta::__ANON__[:2968]
0000s0sFoswiki::Meta::::__ANON__[:2974]Foswiki::Meta::__ANON__[:2974]
0000s0sFoswiki::Meta::::__ANON__[:3046]Foswiki::Meta::__ANON__[:3046]
0000s0sFoswiki::Meta::::__ANON__[:3052]Foswiki::Meta::__ANON__[:3052]
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::::finishFoswiki::Meta::finish
0000s0sFoswiki::Meta::::fireDependencyFoswiki::Meta::fireDependency
0000s0sFoswiki::Meta::::forEachSelectedValueFoswiki::Meta::forEachSelectedValue
0000s0sFoswiki::Meta::::getAttachmentRevisionInfoFoswiki::Meta::getAttachmentRevisionInfo
0000s0sFoswiki::Meta::::getDifferencesFoswiki::Meta::getDifferences
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::::registerMETAFoswiki::Meta::registerMETA
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::::setLeaseFoswiki::Meta::setLease
0000s0sFoswiki::Meta::::setRevisionInfoFoswiki::Meta::setRevisionInfo
0000s0sFoswiki::Meta::::stringifyFoswiki::Meta::stringify
0000s0sFoswiki::Meta::::summariseChangesFoswiki::Meta::summariseChanges
0000s0sFoswiki::Meta::::summariseTextFoswiki::Meta::summariseText
0000s0sFoswiki::Meta::::testAttachmentFoswiki::Meta::testAttachment
0000s0sFoswiki::Meta::::unloadFoswiki::Meta::unload
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
82This module also includes some methods to support embedding meta-data for
83topics directly in topic text, a la the traditional Foswiki store
84(getEmbeddedStoreForm and setEmbeddedStoreForm)
85
86*IMPORTANT* the methods on =Foswiki::Meta= _do not check access permissions_
87(other than =haveAccess=, obviously).
88This is a deliberate design decision, as these checks are expensive and many
89callers don't require them. For this reason, be *very careful* how you use
90=Foswiki::Meta=. Extension authors will almost always find the methods
91they want in =Foswiki::Func=, rather than in this class.
92
93*Since* _date_ indicates where functions or parameters have been added since
94the baseline of the API (Foswiki release 4.2.3). The _date_ indicates the
95earliest date of a Foswiki release that will support that function or
96parameter.
97
98*Deprecated* _date_ indicates where a function or parameters has been
99[[http://en.wikipedia.org/wiki/Deprecation][deprecated]]. Deprecated
100functions will still work, though they should
101_not_ be called in new plugins and should be replaced in older plugins
102as soon as possible. Deprecated parameters are simply ignored in Foswiki
103releases after _date_.
104
105*Until* _date_ indicates where a function or parameter has been removed.
106The _date_ indicates the latest date at which Foswiki releases still supported
107the function or parameter.
108
109=cut
110
111package Foswiki::Meta;
112
113229µs244µs
# spent 29µs (14+15) within Foswiki::Meta::BEGIN@113 which was called: # once (14µs+15µs) by Foswiki::BEGIN@630 at line 113
use strict;
# spent 29µs making 1 call to Foswiki::Meta::BEGIN@113 # spent 15µs making 1 call to strict::import
114228µs222µs
# spent 16µs (9+6) within Foswiki::Meta::BEGIN@114 which was called: # once (9µs+6µs) by Foswiki::BEGIN@630 at line 114
use warnings;
# spent 16µs making 1 call to Foswiki::Meta::BEGIN@114 # spent 6µs making 1 call to warnings::import
115231µs2263µs
# spent 136µs (8+127) within Foswiki::Meta::BEGIN@115 which was called: # once (8µs+127µs) by Foswiki::BEGIN@630 at line 115
use Error qw(:try);
# spent 136µs making 1 call to Foswiki::Meta::BEGIN@115 # spent 127µs making 1 call to Error::import
116229µs237µs
# spent 24µs (10+13) within Foswiki::Meta::BEGIN@116 which was called: # once (10µs+13µs) by Foswiki::BEGIN@630 at line 116
use Assert;
# spent 24µs making 1 call to Foswiki::Meta::BEGIN@116 # spent 13µs making 1 call to Assert::import
117213.1ms21.36ms
# spent 1.24ms (870µs+371µs) within Foswiki::Meta::BEGIN@117 which was called: # once (870µs+371µs) by Foswiki::BEGIN@630 at line 117
use Errno 'EINTR';
# spent 1.24ms making 1 call to Foswiki::Meta::BEGIN@117 # spent 119µs making 1 call to Exporter::import
118
1191200nsour $reason;
120
121# Version for the embedding format (increment when embedding format changes)
1221400nsour $EMBEDDING_FORMAT_VERSION = 1.1;
123
124# defaults for truncation of summary text
1251200nsour $SUMMARY_TMLTRUNC = 162;
1261200nsour $SUMMARY_MINTRUNC = 16;
1271400nsour $SUMMARY_ELLIPSIS = '<b>&hellip;</b>'; # Google style
128
129# the number of characters either side of a search term
1301200nsour $SUMMARY_DEFAULT_CONTEXT = 30;
131
132# max number of lines in a summary (best to keep it even)
1331100nsour $CHANGES_SUMMARY_LINECOUNT = 6;
1341100nsour $CHANGES_SUMMARY_PLAINTRUNC = 70;
135
136=begin TML
137
138PUBLIC %VALIDATE;
139
140META:x validation. This hash maps from META: names to the type record
141registered by registerMETA. See registerMETA for more information on what
142these records contain.
143
144_default is set on base meta-data types (those not added by
145Foswiki::Func::registerMETA) to differentiate the minimum required
146meta-data and that added by extensions.
147
148=cut
149
150116µsour %VALIDATE = (
151 TOPICINFO => {
152 allow => [
153 qw( author version date format reprev
154 rev comment encoding )
155 ],
156 _default => 1,
157 alias => 'info',
158 },
159 TOPICMOVED => {
160 require => [qw( from to by date )],
161 _default => 1,
162 alias => 'moved',
163 },
164
165 # Special case, see Item2554; allow an empty TOPICPARENT, as this was
166 # erroneously generated at some point in the past
167 TOPICPARENT => {
168 allow => [qw( name )],
169 _default => 1,
170 alias => 'parent',
171 },
172 FILEATTACHMENT => {
173 require => [qw( name )],
174 other => [
175 qw( version path size date user
176 comment attr )
177 ],
178 _default => 1,
179 alias => 'attachments',
180 many => 1,
181 },
182 FORM => {
183 require => [qw( name )],
184 _default => 1,
185 alias => 'form',
186 },
187 FIELD => {
188 require => [qw( name value )],
189 other => [qw( title )],
190 _default => 1,
191 alias => 'fields',
192 many => 1,
193 },
194 PREFERENCE => {
195 require => [qw( name value )],
196 other => [qw( type )],
197 _default => 1,
198 alias => 'preferences',
199 many => 1,
200 }
201);
202
203our %aliases =
204 map { $VALIDATE{$_}->{alias} => "META:$_" }
205114µs grep { $VALIDATE{$_}->{alias} } keys %VALIDATE;
206
207our %isArrayType =
208 map { $_ => 1 }
20915µs grep { $VALIDATE{$_}->{many} } keys %VALIDATE;
210
211=begin TML
212
213---++ StaticMethod registerMETA($name, %syntax)
214
215Foswiki supports embedding meta-data into topics. For example,
216
217=%<nop>META:BOOK{title="Transit" author="Edmund Cooper" isbn="0-571-05724-1"}%=
218
219This meta-data is validated when it is read from the store. Meta-data
220that is not registered, or doesn't pass validation, is ignored. This
221function allows you to register a new META datum, passing the name in
222=$name=. =%syntax= contains information about the syntax and semantics of
223the tag.
224
225The following entries are supported in =%syntax=
226
227=many=>1=. By default meta-data are single valued i.e. can only occur once
228in a topic. If you require the meta-data to be repeated many times (like
229META:FIELD and META:ATTACHMENT) then you must set this option. For example,
230to declare a many-valued =BOOK= meta-data type:
231<verbatim>
232registerMeta('BOOK', many => 1)
233</verbatim>
234
235=require=>[]= is used to check that a list of named parameters are present on
236the tag. For example,
237<verbatim>
238registerMETA('BOOK', require => [ 'title', 'author' ]);
239</verbatim>
240can be used to check that both =title= and =author= are present.
241
242=allow=>[]= lets you specify other optional parameters that are allowed
243on the tag. If you specify =allow= then the validation will fail if the
244tag contains any parameters that are _not_ in the =allow= or =require= lists.
245If you don't specify =allow= then all parameters will be allowed.
246
247=require= and =allow= only verify the *presence* of parameters, and
248not their *values*.
249
250=other=[]= lets you declare other legal parameters, and is provided
251mainly to support the initialisation of DB schema. It it is like
252=allow= except that it doesn't imply any exclusion of META that contains
253unallowed params.
254
255=function=>\&fn= causes the function =fn= to be called when the
256datum is encountered when reading a topic, passing in the name of the
257macro and the argument hash. The function must return a non-zero/undef
258value if the tag is acceptable, or 0 otherwise. For example:
259<verbatim>
260registerMETA('BOOK', function => sub {
261 my ($name, $args) = @_;
262 # $name will be BOOK
263 return isValidTitle($args->{title});
264}
265</verbatim>
266can be used to check that =%META:BOOK{}= contains a valid title.
267
268Checks are cumulative, so if you:
269<verbatim>
270registerMETA('BOOK',
271 function => \&checkParameters,
272 require => [ 'title' ],
273 allow => [ 'author', 'isbn' ]);
274</verbatim>
275then all these conditions will be tested. Note that =require= and =allow=
276are tested _after_ =function= is called, to give the function a chance to
277rewrite the parameter list.
278
279If no checker is registered for a META tag, then it will automatically
280be accepted into the topic meta-data.
281
282=alias=>'name'= lets you set an alias for the datum that will be added to
283the query language. For example, =alias=>'info'= is used to alias
284'META:TOPICINFO' in queries.
285<verbatim>
286registerMeta('BOOK', alias => 'book', many => 1)
287</verbatim>
288This lets you use syntax such as =books[author='Anais Nin']= in queries.
289See QuerySearch for more on aliases.
290
291=cut
292
293sub registerMETA {
294 my ( $name, %check ) = @_;
295 $VALIDATE{$name} = \%check;
296 $aliases{ $check{alias} } = "META:$name" if $check{alias};
297 $isArrayType{$name} = $check{many};
298}
299
300############# GENERIC METHODS #############
301
302=begin TML
303
304---++ ClassMethod new($session, $web, $topic [, $text])
305 * =$session= - a Foswiki object (e.g. =$Foswiki::Plugins::SESSION=)
306 * =$web=, =$topic= - the pathname of the object. If both are undef,
307 this object is a handle for the root container. If $topic is undef,
308 it is the handle to a web. Otherwise it's a handle to a topic.
309 * $text - optional raw text, which may include embedded meta-data. Will
310 be passed to =setEmbeddedStoreForm= to initialise the object. Only valid
311 if =$web= and =$topic= are defined.
312Construct a new, unloaded object. This method creates lightweight
313handles for store objects; the full content of the actual object will
314*not* be loaded. If you need to interact with the existing content of
315the stored object, use the =load= method to load the content.
316
317---++ ClassMethod new($prototype)
318
319Construct a new, unloaded object, using the session, web and topic in the
320prototype object (which must be type Foswiki::Meta).
321
322=cut
323
324
# spent 27.9ms (24.0+3.88) within Foswiki::Meta::new which was called 1104 times, avg 25µs/call: # 320 times (10.9ms+1.03ms) by Foswiki::Meta::load at line 428, avg 37µs/call # 229 times (3.03ms+692µs) by Foswiki::Meta::getContainer at line 610, avg 16µs/call # 123 times (2.59ms+594µs) by Foswiki::Prefs::_getBackend at line 140 of /var/www/foswiki11/lib/Foswiki/Prefs.pm, avg 26µs/call # 94 times (2.24ms+471µs) by Foswiki::WebFilter::ok at line 38 of /var/www/foswiki11/lib/Foswiki/WebFilter.pm, avg 29µs/call # 42 times (773µs+173µs) by Foswiki::VAR at line 17 of /var/www/foswiki11/lib/Foswiki/Macros/VAR.pm, avg 23µs/call # 41 times (1.04ms+153µs) by Foswiki::Search::formatResults at line 749 of /var/www/foswiki11/lib/Foswiki/Search.pm, avg 29µs/call # 41 times (588µs+120µs) by Foswiki::Search::searchWeb at line 244 of /var/www/foswiki11/lib/Foswiki/Search.pm, avg 17µs/call # 41 times (544µs+137µs) by Foswiki::Store::QueryAlgorithms::BruteForce::query at line 46 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 17µs/call # 41 times (528µs+127µs) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 167 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 16µs/call # 41 times (535µs+114µs) by Foswiki::Store::QueryAlgorithms::BruteForce::query at line 65 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 16µs/call # 41 times (516µs+111µs) by Foswiki::Search::formatResults at line 902 of /var/www/foswiki11/lib/Foswiki/Search.pm, avg 15µs/call # 40 times (537µs+114µs) by Foswiki::Store::SearchAlgorithms::Forking::query at line 170 of /var/www/foswiki11/lib/Foswiki/Store/SearchAlgorithms/Forking.pm, avg 16µs/call # 3 times (69µs+11µs) by Foswiki::REVINFO at line 27 of /var/www/foswiki11/lib/Foswiki/Macros/REVINFO.pm, avg 27µs/call # 2 times (53µs+14µs) by Foswiki::deepWebList at line 1568 of /var/www/foswiki11/lib/Foswiki.pm, avg 33µs/call # 2 times (44µs+10µs) by Foswiki::_renderZone at line 3502 of /var/www/foswiki11/lib/Foswiki.pm, avg 27µs/call # 2 times (29µs+7µs) by Foswiki::Func::expandCommonVariables at line 2531 of /var/www/foswiki11/lib/Foswiki/Func.pm, avg 18µs/call # once (44µs+7µs) by Foswiki::_lookupIcon at line 31 of /var/www/foswiki11/lib/Foswiki/Macros/ICON.pm
sub new {
3251104955µs my ( $class, $session, $web, $topic, $text ) = @_;
326
32711044.74ms11041.72ms if ( $session->isa('Foswiki::Meta') ) {
# spent 1.72ms making 1104 calls to UNIVERSAL::isa, avg 2µs/call
328
329 # Prototype
330 ASSERT( !defined($web) && !defined($topic) && !defined($text) )
331 if DEBUG;
332 return $class->new( $session->session, $session->web, $session->topic );
333 }
334
33511042.86ms my $this = bless(
336 {
337 _session => $session,
338
339 # Index keyed on top level type mapping entry names to their
340 # index within the data array.
341 _indices => undef,
342 },
343 ref($class) || $class
344 );
345
346 # Normalise web path (replace [./]+ with /)
3471104495µs if ( defined $web ) {
34811041.41ms11041.31ms ASSERT( UNTAINTED($web), 'web is tainted' ) if DEBUG;
# spent 1.31ms making 1104 calls to Assert::ASSERTS_OFF, avg 1µs/call
3491104866µs $web =~ tr#/.#/#s;
350 }
351
352 # Note: internal fields are prepended with _. All uppercase
353 # fields will be assumed to be meta-data.
354
35511041.07ms $this->{_web} = $web;
356
35711041.04ms1104859µs ASSERT( UNTAINTED($topic), 'topic is tainted' )
# spent 859µs making 1104 calls to Assert::ASSERTS_OFF, avg 778ns/call
358 if ( DEBUG && defined $topic );
359
3601104823µs $this->{_topic} = $topic;
361
362 #print STDERR "--new Meta($web, ".($topic||'undef').")\n";
363 #$this->{_text} = undef; # topics only
364
365 # Preferences cache object. We store a pointer, rather than looking
366 # up the name each time, because we want to be able to invalidate the
367 # loaded preferences if this object is loaded.
368 #$this->{_preferences} = undef;
369
3701104905µs $this->{FILEATTACHMENT} = [];
371
3721104195µs if ( defined $text ) {
373
374 # User supplied topic body forces us to consider this as the
375 # latest rev
376 ASSERT( defined($web), 'web is not defined' ) if DEBUG;
377 ASSERT( defined($topic), 'topic is not defined' ) if DEBUG;
378 $this->setEmbeddedStoreForm($text);
379 $this->{_latestIsLoaded} = 1;
380 }
381
38211043.12ms return $this;
383}
384
385=begin TML
386
387---++ ClassMethod load($session, $web, $topic, $rev)
388
389This constructor will load (or otherwise fetch) the meta-data for a
390named web/topic.
391 * =$rev= - revision to load. If undef, 0, '' or > max available rev, will
392 load the latest rev.
393
394This method is functionally identical to:
395<verbatim>
396$this = Foswiki::Meta->new( $session, $web, $topic );
397$this->loadVersion( $rev );
398</verbatim>
399
400WARNING: see notes on revision numbers under =getLoadedRev=
401
402---++ ObjectMethod load($rev) -> $metaObject
403
404Load an unloaded meta-data object with a given version of the data.
405Once loaded, the object is locked to that revision.
406
407 * =$rev= - revision to load. If undef, 0, '' or > max available rev, will
408 load the latest rev.
409
410WARNING: see notes on revision numbers under =getLoadedRev=
411
412=cut
413
414
# spent 1.38s (2.85ms+1.37) within Foswiki::Meta::load which was called 321 times, avg 4.29ms/call: # 305 times (2.64ms+775ms) by Foswiki::MetaCache::get at line 180 of /var/www/foswiki11/lib/Foswiki/MetaCache.pm, avg 2.55ms/call # 6 times (95µs+588ms) by Foswiki::INCLUDE at line 205 of /var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm, avg 98.1ms/call # 3 times (33µs+1.97ms) by Foswiki::Users::TopicUserMapping::eachGroupMember at line 704 of /var/www/foswiki11/lib/Foswiki/Users/TopicUserMapping.pm, avg 669µs/call # 2 times (20µs+5.54ms) by Foswiki::Func::readTopicText at line 3492 of /var/www/foswiki11/lib/Foswiki/Func.pm, avg 2.78ms/call # once (14µs+730µs) by Foswiki::Users::TopicUserMapping::_loadMapping at line 1683 of /var/www/foswiki11/lib/Foswiki/Users/TopicUserMapping.pm # once (12µs+600µs) by Foswiki::Func::checkAccessPermission at line 1421 of /var/www/foswiki11/lib/Foswiki/Func.pm # once (13µs+586µs) by Foswiki::Func::readTopic at line 1594 of /var/www/foswiki11/lib/Foswiki/Func.pm # once (14µs+576µs) by Foswiki::QUERY at line 21 of /var/www/foswiki11/lib/Foswiki/Macros/QUERY.pm # once (9µs+402µs) by Foswiki::UI::View::view at line 125 of /var/www/foswiki11/lib/Foswiki/UI/View.pm
sub load {
415321100µs my $proto = shift;
41632137µs my $this;
41732133µs my $rev;
418
419321132µs if ( ref($proto) ) {
420
421 # Existing unloaded object
42211µs1800ns ASSERT( !$this->{_loadedRev} ) if DEBUG;
# spent 800ns making 1 call to Assert::ASSERTS_OFF
4231500ns $this = $proto;
4241800ns $rev = shift;
425 }
426 else {
427320453µs ( my $session, my $web, my $topic, $rev ) = @_;
428320707µs32011.9ms $this = $proto->new( $session, $web, $topic );
# spent 11.9ms making 320 calls to Foswiki::Meta::new, avg 37µs/call
429 }
430321434µs3211.36s $this->loadVersion($rev);
# spent 1.36s making 321 calls to Foswiki::Meta::loadVersion, avg 4.24ms/call
431
432321745µs return $this;
433}
434
435=begin TML
436
437---++ ObjectMethod unload()
438
439Return the object to an unloaded state. This method should be used
440with the greatest of care, as it resets the load state of the object,
441which may have surprising effects on other code that shares the object.
442
443=cut
444
445sub unload {
446 my $this = shift;
447
448 $this->{_session}->search->metacache->removeMeta( $this->web, $this->topic )
449 if $this->{_session};
450 $this->{_loadedRev} = undef;
451 $this->{_latestIsLoaded} = undef;
452 $this->{_text} = undef;
453 $this->{_preferences}->finish() if defined $this->{_preferences};
454 undef $this->{_preferences};
455 $this->{_preferences} = undef;
456
457 # Unload meta-data
458 foreach my $type ( keys %{ $this->{_indices} } ) {
459 delete $this->{$type};
460 }
461 undef $this->{_indices};
462}
463
464=begin TML
465
466---++ ObjectMethod finish()
467Clean up the object, releasing any memory stored in it. Make sure this
468gets called before an object you have created goes out of scope.
469
470=cut
471
472# Note to developers; please undef *all* fields in the object explicitly,
473# whether they are references or not. That way this method is "golden
474# documentation" of the live fields in the object.
475sub finish {
476 my $this = shift;
477 $this->unload();
478 undef $this->{_web};
479 undef $this->{_topic};
480 undef $this->{_session};
481}
482
483=begin TML
484
485---++ ObjectMethod session()
486
487Get the session associated with the object when it was created.
488
489=cut
490
491
# spent 96µs within Foswiki::Meta::session which was called 45 times, avg 2µs/call: # 18 times (37µs+0s) by Foswiki::If::OP_dollar::evaluate at line 30 of /var/www/foswiki11/lib/Foswiki/If/OP_dollar.pm, avg 2µs/call # 12 times (19µs+0s) by Foswiki::If::OP_defined::evaluate at line 27 of /var/www/foswiki11/lib/Foswiki/If/OP_defined.pm, avg 2µs/call # 8 times (29µs+0s) by Foswiki::If::OP_context::evaluate at line 32 of /var/www/foswiki11/lib/Foswiki/If/OP_context.pm, avg 4µs/call # 7 times (11µs+0s) by Foswiki::If::OP_istopic::evaluate at line 27 of /var/www/foswiki11/lib/Foswiki/If/OP_istopic.pm, avg 2µs/call
sub session {
49245151µs return $_[0]->{_session};
493}
494
495# Assert helper
496sub _assertIsTopic {
497 my $this = shift;
498 ASSERT( $this->isa('Foswiki::Meta') );
499 ASSERT( defined $this->{_web} && $this->{_topic}, 'not a topic object' );
500}
501
502sub _assertIsWeb {
503 my $this = shift;
504 ASSERT( $this->isa('Foswiki::Meta') );
505 ASSERT( $this->{_web} && !$this->{_topic}, 'not a web object' );
506}
507
508=begin TML
509
510---++ ObjectMethod web([$name])
511 * =$name= - optional, change the web name in the object
512 * *Since* 28 Nov 2008
513Get/set the web name associated with the object.
514
515=cut
516
517
# spent 143ms within Foswiki::Meta::web which was called 56513 times, avg 3µs/call: # 46125 times (116ms+0s) by Foswiki::Search::InfoCache::__ANON__[/var/www/foswiki11/lib/Foswiki/Search/InfoCache.pm:579] at line 577 of /var/www/foswiki11/lib/Foswiki/Search/InfoCache.pm, avg 3µs/call # 8760 times (23.7ms+0s) by Foswiki::Search::InfoCache::addTopic at line 92 of /var/www/foswiki11/lib/Foswiki/Search/InfoCache.pm, avg 3µs/call # 892 times (1.53ms+0s) by Foswiki::Prefs::loadPreferences at line 232 of /var/www/foswiki11/lib/Foswiki/Prefs.pm, avg 2µs/call # 470 times (886µs+0s) by Foswiki::Store::VC::Handler::new at line 72 of /var/www/foswiki11/lib/Foswiki/Store/VC/Handler.pm, avg 2µs/call # 85 times (215µs+0s) by Foswiki::Search::formatResult at line 1124 of /var/www/foswiki11/lib/Foswiki/Search.pm, avg 3µs/call # 49 times (115µs+0s) by Foswiki::innerExpandMacros at line 2807 of /var/www/foswiki11/lib/Foswiki.pm, avg 2µs/call # 40 times (96µs+0s) by Foswiki::SEARCH at line 12 of /var/www/foswiki11/lib/Foswiki/Macros/SEARCH.pm, avg 2µs/call # 17 times (52µs+0s) by Foswiki::Render::_handleSquareBracketedLink at line 902 of /var/www/foswiki11/lib/Foswiki/Render.pm, avg 3µs/call # 7 times (18µs+0s) by Foswiki::expandMacros at line 3332 of /var/www/foswiki11/lib/Foswiki.pm, avg 3µs/call # 7 times (16µs+0s) by Foswiki::INCLUDE at line 148 of /var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm, avg 2µs/call # 7 times (16µs+0s) by Foswiki::expandMacros at line 3317 of /var/www/foswiki11/lib/Foswiki.pm, avg 2µs/call # 7 times (14µs+0s) by Foswiki::INCLUDE at line 172 of /var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm, avg 2µs/call # 6 times (37µs+0s) by Foswiki::__ANON__[/var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm:331] at line 227 of /var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm, avg 6µs/call # 6 times (20µs+0s) by Foswiki::Prefs::popTopicContext at line 314 of /var/www/foswiki11/lib/Foswiki/Prefs.pm, avg 3µs/call # 6 times (19µs+0s) by Foswiki::__ANON__[/var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm:331] at line 314 of /var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm, avg 3µs/call # 6 times (15µs+0s) by Foswiki::INCLUDE at line 190 of /var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm, avg 3µs/call # 5 times (15µs+0s) by Foswiki::Render::getRenderedVersion at line 1158 of /var/www/foswiki11/lib/Foswiki/Render.pm, avg 3µs/call # 4 times (9µs+0s) by Foswiki::Func::__ANON__[/var/www/foswiki11/lib/Foswiki/Func.pm:611] at line 609 of /var/www/foswiki11/lib/Foswiki/Func.pm, avg 2µs/call # 3 times (6µs+0s) by Foswiki::REVINFO at line 13 of /var/www/foswiki11/lib/Foswiki/Macros/REVINFO.pm, avg 2µs/call # 3 times (5µs+0s) by Foswiki::REVINFO at line 22 of /var/www/foswiki11/lib/Foswiki/Macros/REVINFO.pm, avg 2µs/call # 2 times (12µs+0s) by Foswiki::UI::View::revisionsAround at line 492 of /var/www/foswiki11/lib/Foswiki/UI/View.pm, avg 6µs/call # 2 times (7µs+0s) by Foswiki::Store::VC::Store::eachWeb at line 426 of /var/www/foswiki11/lib/Foswiki/Store/VC/Store.pm, avg 4µs/call # 2 times (7µs+0s) by Foswiki::UI::View::revisionsAround at line 480 of /var/www/foswiki11/lib/Foswiki/UI/View.pm, avg 4µs/call # once (7µs+0s) by Foswiki::UI::View::view at line 213 of /var/www/foswiki11/lib/Foswiki/UI/View.pm # once (3µs+0s) by Foswiki::Render::renderParent at line 163 of /var/www/foswiki11/lib/Foswiki/Render.pm
sub web {
5185651332.7ms my ( $this, $web ) = @_;
5195651311.0ms $this->{_web} = $web if defined $web;
52056513249ms return $this->{_web};
521}
522
523=begin TML
524
525---++ ObjectMethod topic([$name])
526 * =$name= - optional, change the topic name in the object
527 * *Since* 28 Nov 2008
528Get/set the topic name associated with the object.
529
530=cut
531
532
# spent 49.3ms within Foswiki::Meta::topic which was called 18945 times, avg 3µs/call: # 8760 times (29.7ms+0s) by Foswiki::Store::QueryAlgorithms::BruteForce::getField at line 273 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 3µs/call # 8760 times (16.7ms+0s) by Foswiki::Search::InfoCache::addTopic at line 93 of /var/www/foswiki11/lib/Foswiki/Search/InfoCache.pm, avg 2µs/call # 679 times (1.25ms+0s) by Foswiki::Prefs::loadPreferences at line 232 of /var/www/foswiki11/lib/Foswiki/Prefs.pm, avg 2µs/call # 470 times (1.08ms+0s) by Foswiki::Store::VC::Handler::new at line 71 of /var/www/foswiki11/lib/Foswiki/Store/VC/Handler.pm, avg 2µs/call # 85 times (183µs+0s) by Foswiki::Search::formatResult at line 1125 of /var/www/foswiki11/lib/Foswiki/Search.pm, avg 2µs/call # 49 times (111µs+0s) by Foswiki::innerExpandMacros at line 2808 of /var/www/foswiki11/lib/Foswiki.pm, avg 2µs/call # 42 times (105µs+0s) by Foswiki::VAR at line 12 of /var/www/foswiki11/lib/Foswiki/Macros/VAR.pm, avg 2µs/call # 40 times (85µs+0s) by Foswiki::SEARCH at line 13 of /var/www/foswiki11/lib/Foswiki/Macros/SEARCH.pm, avg 2µs/call # 7 times (19µs+0s) by Foswiki::expandMacros at line 3333 of /var/www/foswiki11/lib/Foswiki.pm, avg 3µs/call # 7 times (17µs+0s) by Foswiki::expandMacros at line 3317 of /var/www/foswiki11/lib/Foswiki.pm, avg 2µs/call # 7 times (14µs+0s) by Foswiki::INCLUDE at line 149 of /var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm, avg 2µs/call # 6 times (19µs+0s) by Foswiki::__ANON__[/var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm:331] at line 227 of /var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm, avg 3µs/call # 6 times (15µs+0s) by Foswiki::Prefs::popTopicContext at line 314 of /var/www/foswiki11/lib/Foswiki/Prefs.pm, avg 2µs/call # 6 times (11µs+0s) by Foswiki::INCLUDE at line 190 of /var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm, avg 2µs/call # 5 times (11µs+0s) by Foswiki::Render::getRenderedVersion at line 1158 of /var/www/foswiki11/lib/Foswiki/Render.pm, avg 2µs/call # 4 times (10µs+0s) by Foswiki::Func::__ANON__[/var/www/foswiki11/lib/Foswiki/Func.pm:611] at line 609 of /var/www/foswiki11/lib/Foswiki/Func.pm, avg 3µs/call # 3 times (6µs+0s) by Foswiki::REVINFO at line 14 of /var/www/foswiki11/lib/Foswiki/Macros/REVINFO.pm, avg 2µs/call # 3 times (5µs+0s) by Foswiki::REVINFO at line 22 of /var/www/foswiki11/lib/Foswiki/Macros/REVINFO.pm, avg 2µs/call # 2 times (7µs+0s) by Foswiki::UI::View::revisionsAround at line 492 of /var/www/foswiki11/lib/Foswiki/UI/View.pm, avg 4µs/call # 2 times (5µs+0s) by Foswiki::UI::View::revisionsAround at line 480 of /var/www/foswiki11/lib/Foswiki/UI/View.pm, avg 3µs/call # once (4µs+0s) by Foswiki::UI::View::view at line 213 of /var/www/foswiki11/lib/Foswiki/UI/View.pm # once (2µs+0s) by Foswiki::Render::renderParent at line 163 of /var/www/foswiki11/lib/Foswiki/Render.pm
sub topic {
533189459.41ms my ( $this, $topic ) = @_;
534189453.41ms $this->{_topic} = $topic if defined $topic;
5351894575.2ms return $this->{_topic};
536}
537
538=begin TML
539
540---++ ObjectMethod getPath() -> $objectpath
541
542Get the canonical content access path for the object
543
544=cut
545
546
# spent 2.10ms within Foswiki::Meta::getPath which was called 808 times, avg 3µs/call: # 679 times (1.58ms+0s) by Foswiki::Prefs::loadPreferences at line 224 of /var/www/foswiki11/lib/Foswiki/Prefs.pm, avg 2µs/call # 123 times (482µs+0s) by Foswiki::Prefs::_getBackend at line 142 of /var/www/foswiki11/lib/Foswiki/Prefs.pm, avg 4µs/call # 5 times (31µs+0s) by Foswiki::Render::getAnchorNames at line 2226 of /var/www/foswiki11/lib/Foswiki/Render.pm, avg 6µs/call # once (8µs+0s) by Foswiki::_lookupIcon at line 48 of /var/www/foswiki11/lib/Foswiki/Macros/ICON.pm
sub getPath {
547808256µs my $this = shift;
548808355µs my $path = $this->{_web};
549
550808107µs return '' unless $path;
5518081.36ms return $path unless $this->{_topic};
552362200µs $path .= '.' . $this->{_topic};
553362927µs return $path;
554}
555
556=begin TML
557
558---++ ObjectMethod isSessionTopic() -> $boolean
559Return true if this object refers to the session topic
560
561=cut
562
563sub isSessionTopic {
564 my $this = shift;
565 return 0
566 unless defined $this->{_web}
567 && defined $this->{_topic}
568 && defined $this->{_session}->{webName}
569 && defined $this->{_session}->{topicName};
570 return $this->{_web} eq $this->{_session}->{webName}
571 && $this->{_topic} eq $this->{_session}->{topicName};
572}
573
574=begin TML
575
576---++ ObjectMethod getPreference( $key ) -> $value
577
578Get a value for a preference defined in the object. Note that
579web preferences always inherit from parent webs, but topic preferences
580are strictly local to topics.
581
582=cut
583
584
# spent 8.31s (7.73ms+8.30) within Foswiki::Meta::getPreference which was called 1336 times, avg 6.22ms/call: # 1104 times (5.32ms+344ms) by Foswiki::Meta::_getACL at line 1706, avg 317µs/call # 94 times (1.11ms+7.94s) by Foswiki::WebFilter::ok at line 39 of /var/www/foswiki11/lib/Foswiki/WebFilter.pm, avg 84.5ms/call # 42 times (379µs+2.43ms) by Foswiki::VAR at line 20 of /var/www/foswiki11/lib/Foswiki/Macros/VAR.pm, avg 67µs/call # 41 times (559µs+11.0ms) by Foswiki::Store::QueryAlgorithms::BruteForce::query at line 66 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 281µs/call # 40 times (251µs+1.59ms) by Foswiki::Store::SearchAlgorithms::Forking::query at line 171 of /var/www/foswiki11/lib/Foswiki/Store/SearchAlgorithms/Forking.pm, avg 46µs/call # 12 times (73µs+21µs) by Foswiki::If::OP_defined::evaluate at line 34 of /var/www/foswiki11/lib/Foswiki/If/OP_defined.pm, avg 8µs/call # 3 times (38µs+580µs) by Foswiki::Users::TopicUserMapping::eachGroupMember at line 719 of /var/www/foswiki11/lib/Foswiki/Users/TopicUserMapping.pm, avg 206µs/call
sub getPreference {
5851336567µs my ( $this, $key ) = @_;
586
5871336444µs unless ( $this->{_web} || $this->{_topic} ) {
588 return $this->{_session}->{prefs}->getPreference($key);
589 }
590
591 # make sure the preferences are parsed and cached
59213362.02ms6798.27s unless ( $this->{_preferences} ) {
# spent 8.27s making 679 calls to Foswiki::Prefs::loadPreferences, avg 12.2ms/call
593 $this->{_preferences} =
594 $this->{_session}->{prefs}->loadPreferences($this);
595 }
59613369.65ms133629.5ms return $this->{_preferences}->get($key);
# spent 28.8ms making 861 calls to Foswiki::Prefs::Web::get, avg 33µs/call # spent 707µs making 475 calls to Foswiki::Prefs::TopicRAM::get, avg 1µs/call
597}
598
599=begin TML
600
601---++ ObjectMethod getContainer() -> $containerObject
602
603Get the container of this object; for example, the web that a topic is within
604
605=cut
606
607
# spent 4.92ms (1.20+3.72) within Foswiki::Meta::getContainer which was called 229 times, avg 21µs/call: # 229 times (1.20ms+3.72ms) by Foswiki::Meta::haveAccess at line 1788, avg 21µs/call
sub getContainer {
60822962µs my $this = shift;
609
6102291.03ms2293.72ms if ( $this->{_topic} ) {
# spent 3.72ms making 229 calls to Foswiki::Meta::new, avg 16µs/call
611 return Foswiki::Meta->new( $this->{_session}, $this->{_web} );
612 }
613 if ( $this->{_web} ) {
614 return Foswiki::Meta->new( $this->{_session} );
615 }
616 ASSERT( 0, 'no container for this object type' ) if DEBUG;
617 return;
618}
619
620=begin TML
621
622---++ ObjectMethod existsInStore() -> $boolean
623
624A Meta object can be created for a web or topic that doesn't exist in the
625actual store (e.g. is in the process of being created). This method returns
626true if the corresponding web or topic really exists in the store.
627
628=cut
629
630
# spent 23.5ms (2.76+20.8) within Foswiki::Meta::existsInStore which was called 336 times, avg 70µs/call: # 336 times (2.76ms+20.8ms) by Foswiki::Prefs::TopicRAM::new at line 31 of /var/www/foswiki11/lib/Foswiki/Prefs/TopicRAM.pm, avg 70µs/call
sub existsInStore {
63133697µs my $this = shift;
632336177µs if ( defined $this->{_topic} ) {
633
634 # only checking for a topic existence already establishes a dependency
635336326µs336590µs $this->addDependency();
# spent 590µs making 336 calls to Foswiki::Meta::addDependency, avg 2µs/call
636
6373361.47ms33620.2ms return $this->{_session}->{store}
# spent 20.2ms making 336 calls to Foswiki::Store::VC::Store::topicExists, avg 60µs/call
638 ->topicExists( $this->{_web}, $this->{_topic} );
639 }
640 elsif ( defined $this->{_web} ) {
641 return $this->{_session}->{store}->webExists( $this->{_web} );
642 }
643 else {
644 return 1; # the root always exists
645 }
646}
647
648=begin TML
649
650---++ ObjectMethod stringify( $debug ) -> $string
651
652Return a string version of the meta object. $debug adds
653extra debug info.
654
655=cut
656
657sub stringify {
658 my ( $this, $debug ) = @_;
659 my $s = $this->{_web};
660 if ( $this->{_topic} ) {
661 $s .= ".$this->{_topic} ";
662 $s .=
663 ( defined $this->{_loadedRev} )
664 ? $this->{_loadedRev}
665 : '(not loaded)'
666 if $debug;
667 $s .= "\n" . $this->getEmbeddedStoreForm();
668 }
669 return $s;
670}
671
672=begin TML
673
674---++ ObjectMethod addDependency() -> $this
675
676This establishes a dependency between $this and the
677base topic this session is currently rendering. The dependency
678will be asserted during Foswiki::PageCache::cachePage().
679See Foswiki::PageCache::addDependency().
680
681=cut
682
683
# spent 1.78ms within Foswiki::Meta::addDependency which was called 762 times, avg 2µs/call: # 426 times (1.20ms+0s) by Foswiki::Meta::loadVersion at line 975, avg 3µs/call # 336 times (590µs+0s) by Foswiki::Meta::existsInStore at line 635, avg 2µs/call
sub addDependency {
684762815µs my $cache = $_[0]->{_session}->{cache};
6857622.08ms return unless $cache;
686 return $cache->addDependency( $_[0]->{_web}, $_[0]->{_topic} );
687}
688
689=begin TML
690
691---++ ObjectMethod fireDependency() -> $this
692
693Invalidates the cache bucket of the current meta object
694within the Foswiki::PageCache. See Foswiki::PageCache::fireDependency().
695
696=cut
697
698sub fireDependency {
699 my $cache = $_[0]->{_session}->{cache};
700 return unless $cache;
701 return $cache->fireDependency( $_[0]->{_web}, $_[0]->{_topic} );
702}
703
704############# WEB METHODS #############
705
706=begin TML
707
708---++ ObjectMethod populateNewWeb( [$baseWeb [, $opts]] )
709
710$baseWeb is the name of an existing web (a template web). If the
711base web is a system web, all topics in it
712will be copied into this web. If it is a normal web, only topics starting
713with 'Web' will be copied. If no base web is specified, an empty web
714(with no topics) will be created. If it is specified but does not exist,
715an error will be thrown.
716
717$opts is a ref to a hash that contains settings to be modified in
718the web preferences topic in the new web.
719
720#SMELL: there seems to be no reason to call this method 'NewWeb', it can be used to copy into an existing web
721and it does not appear to be unexpectedly destructive.
722perhaps refactor into something that takes a resultset as an input list? (users have asked to be able to copy a SEARCH'd set of topics..)
723
724=cut
725
726sub populateNewWeb {
727 my ( $this, $templateWeb, $opts ) = @_;
728 _assertIsWeb($this) if DEBUG;
729
730 my $session = $this->{_session};
731
732 my ( $parent, $new ) = $this->{_web} =~ m/^(.*)\/([^\.\/]+)$/;
733
734 if ($parent) {
735 unless ( $Foswiki::cfg{EnableHierarchicalWebs} ) {
736 throw Error::Simple( 'Unable to create '
737 . $this->{_web}
738 . ' - Hierarchical webs are disabled' );
739 }
740
741 unless ( $session->webExists($parent) ) {
742 throw Error::Simple( 'Parent web ' . $parent . ' does not exist' );
743 }
744 }
745
746 # Validate that template web exists, or error should be thrown
747 if ($templateWeb) {
748 unless ( $session->webExists($templateWeb) ) {
749 throw Error::Simple(
750 'Template web ' . $templateWeb . ' does not exist' );
751 }
752 }
753
754 # Make sure there is a preferences topic; this is how we know it's a web
755 my $prefsTopicObject;
756 if (
757 !$session->topicExists(
758 $this->{_web}, $Foswiki::cfg{WebPrefsTopicName}
759 )
760 )
761 {
762 my $prefsText = 'Preferences';
763 $prefsTopicObject =
764 $this->new( $this->{_session}, $this->{_web},
765 $Foswiki::cfg{WebPrefsTopicName}, $prefsText );
766 $prefsTopicObject->save();
767 }
768
769 if ($templateWeb) {
770 my $tWebObject = $this->new( $session, $templateWeb );
771 require Foswiki::WebFilter;
772 my $sys =
773 Foswiki::WebFilter->new('template')->ok( $session, $templateWeb );
774 my $it = $tWebObject->eachTopic();
775 while ( $it->hasNext() ) {
776 my $topic = $it->next();
777 next unless ( $sys || $topic =~ /^Web/ );
778 my $to =
779 Foswiki::Meta->load( $this->{_session}, $templateWeb, $topic );
780 $to->saveAs( $this->{_web}, $topic, ( forcenewrevision => 1 ) );
781 }
782 }
783
784 # patch WebPreferences in new web. We ignore permissions, because
785 # we are creating a new web here.
786 if ($opts) {
787 my $prefsTopicObject =
788 Foswiki::Meta->load( $this->{_session}, $this->{_web},
789 $Foswiki::cfg{WebPrefsTopicName} );
790 my $text = $prefsTopicObject->text;
791 foreach my $key ( keys %$opts ) {
792
793 #don't create the required params to create web.
794 next if ( $key eq 'BASEWEB' );
795 next if ( $key eq 'NEWWEB' );
796 next if ( $key eq 'NEWTOPIC' );
797 next if ( $key eq 'ACTION' );
798
799 if ( defined( $opts->{$key} ) ) {
800 if ( $text =~
801s/($Foswiki::regex{setRegex}$key\s*=).*?$/$1 $opts->{$key}/gm
802 )
803 {
804 }
805 else {
806
807 #this setting wasn't found, so we need to append it.
808 $text .= "\n * Web Created with KEY set\n";
809 $text .= "\n * Set $key = $opts->{$key}\n";
810 }
811 }
812 }
813 $prefsTopicObject->text($text);
814 $prefsTopicObject->save();
815 }
816}
817
818=begin TML
819
820---++ StaticMethod query($query, $inputTopicSet, \%options) -> $outputTopicSet
821
822Search for topic information
823=$query= must be a =Foswiki::*::Node= object.
824
825 * $inputTopicSet is a reference to an iterator containing a list
826 of topic in this web, if set to undef, the search/query algo will
827 create a new iterator using eachTopic()
828 and the web, topic and excludetopics options (as per SEARCH)
829 * web option - The web/s to search in - string can have the same form
830 as the =web= param of SEARCH
831
832
833Returns an Foswiki::Search::InfoCache iterator
834
835=cut
836
837
# spent 48.7s (360µs+48.7) within Foswiki::Meta::query which was called 41 times, avg 1.19s/call: # 41 times (360µs+48.7s) by Foswiki::Search::searchWeb at line 356 of /var/www/foswiki11/lib/Foswiki/Search.pm, avg 1.19s/call
sub query {
8384146µs my ( $query, $inputTopicSet, $options ) = @_;
83941302µs4148.7s return $Foswiki::Plugins::SESSION->{store}
# spent 48.7s making 41 calls to Foswiki::Store::VC::Store::query, avg 1.19s/call
840 ->query( $query, $inputTopicSet, $Foswiki::Plugins::SESSION, $options );
841}
842
843=begin TML
844
845---++ ObjectMethod eachWeb( $all ) -> $iterator
846
847Return an iterator over each subweb. If =$all= is set, will return a
848list of all web names *under* the current location. Returns web pathnames
849relative to $this.
850
851Only valid on webs and the root.
852
853=cut
854
855
# spent 92.1ms (56µs+92.0) within Foswiki::Meta::eachWeb which was called 2 times, avg 46.0ms/call: # 2 times (56µs+92.0ms) by Foswiki::deepWebList at line 1569 of /var/www/foswiki11/lib/Foswiki.pm, avg 46.0ms/call
sub eachWeb {
85624µs my ( $this, $all ) = @_;
857
858 # Works on the root, so {_web} may be undef
85924µs23µs ASSERT( !$this->{_topic}, 'this object may not contain webs' ) if DEBUG;
# spent 3µs making 2 calls to Assert::ASSERTS_OFF, avg 1µs/call
860240µs292.0ms return $this->{_session}->{store}->eachWeb( $this, $all );
# spent 92.0ms making 2 calls to Foswiki::Store::VC::Store::eachWeb, avg 46.0ms/call
861
862}
863
864=begin TML
865
866---++ ObjectMethod eachTopic() -> $iterator
867
868Return an iterator over each topic name in the web. Only valid on webs.
869
870=cut
871
872
# spent 330ms (442µs+329) within Foswiki::Meta::eachTopic which was called 41 times, avg 8.05ms/call: # 41 times (442µs+329ms) by Foswiki::Search::InfoCache::getTopicListIterator at line 560 of /var/www/foswiki11/lib/Foswiki/Search/InfoCache.pm, avg 8.05ms/call
sub eachTopic {
8734126µs my ($this) = @_;
8744138µs4130µs _assertIsWeb($this) if DEBUG;
# spent 30µs making 41 calls to Assert::ASSERTS_OFF, avg 737ns/call
875
8764122µs if ( !$this->{_web} ) {
877
878 # Root
879 require Foswiki::ListIterator;
880 return new Foswiki::ListIterator( [] );
881 }
88241268µs41329ms return $this->{_session}->{store}->eachTopic($this);
# spent 329ms making 41 calls to Foswiki::Store::VC::Store::eachTopic, avg 8.03ms/call
883}
884
885=begin TML
886
887---++ ObjectMethod eachAttachment() -> $iterator
888
889Return an iterator over each attachment name in the topic.
890Only valid on topics.
891
892The list of the names of attachments stored for the given topic may be a
893longer list than the list that comes from the topic meta-data, which may
894only lists the attachments that are normally visible to the user.
895
896=cut
897
898sub eachAttachment {
899 my ($this) = @_;
900 _assertIsTopic($this) if DEBUG;
901 return $this->{_session}->{store}->eachAttachment($this);
902}
903
904=begin TML
905
906---++ ObjectMethod eachChange( $time ) -> $iterator
907
908Get an iterator over the list of all the changes in the web between
909=$time= and now. $time is a time in seconds since 1st Jan 1970, and is not
910guaranteed to return any changes that occurred before (now -
911{Store}{RememberChangesFor}). Changes are returned in most-recent-first
912order.
913
914Only valid for a web.
915
916=cut
917
918sub eachChange {
919 my ( $this, $time ) = @_;
920
921 # not valid at root level
922 _assertIsWeb($this) if DEBUG;
923 return $this->{_session}->{store}->eachChange( $this, $time );
924}
925
926############# TOPIC METHODS #############
927
928=begin TML
929
930---++ ObjectMethod loadVersion($rev) -> $version
931
932Load the object from the store. The object must not be already loaded
933with a different rev (verified by an ASSERT)
934
935See =getLoadedRev= to determine what revision is currently being viewed.
936 * =$rev= - revision to load. If undef, 0, '' or > max available rev, will
937 load the latest rev.
938
939Returns the version identifier for the loaded revision.
940
941WARNING: see notes on revision numbers under =getLoadedRev=
942
943=cut
944
945
# spent 11.0s (7.21ms+11.0) within Foswiki::Meta::loadVersion which was called 426 times, avg 25.8ms/call: # 321 times (4.64ms+1.36s) by Foswiki::Meta::load at line 430, avg 4.24ms/call # 102 times (2.50ms+9.61s) by Foswiki::Meta::text at line 999, avg 94.2ms/call # 3 times (64µs+1.34ms) by Foswiki::Meta::haveAccess at line 1735, avg 468µs/call
sub loadVersion {
946426240µs my ( $this, $rev ) = @_;
947426161µs return unless $this->{_topic};
948
949 # If no specific rev was requested, check that the latest rev is
950 # loaded.
951426275µs if ( !defined $rev || !$rev ) {
952
953 # Trying to load the latest
954426151µs return if $this->{_latestIsLoaded};
955426396µs426322µs ASSERT( !defined( $this->{_loadedRev} ) ) if DEBUG;
# spent 322µs making 426 calls to Assert::ASSERTS_OFF, avg 756ns/call
956 }
957 elsif ( defined( $this->{_loadedRev} ) ) {
958
959 # Cannot load a different rev into an already-loaded
960 # Foswiki::Meta object
961 $rev = -1 unless defined $rev;
962 ASSERT( 0, "Attempt to reload $rev over version $this->{_loadedRev}" );
963 }
964
965 # Is it already loaded?
966426404µs426296µs ASSERT( !($rev) or $rev =~ /^\s*\d+\s*/ ) if DEBUG; # looks like a number
# spent 296µs making 426 calls to Assert::ASSERTS_OFF, avg 696ns/call
96742679µs return if ( $rev && $this->{_loadedRev} && $rev == $this->{_loadedRev} );
9684261.84ms42611.0s ( $this->{_loadedRev}, $this->{_latestIsLoaded} ) =
# spent 11.0s making 426 calls to Foswiki::Store::VC::Store::readTopic, avg 25.7ms/call
969 $this->{_session}->{store}->readTopic( $this, $rev );
970
971 # Make sure text always has a value once loadVersion has been called
972 # once.
973426154µs $this->{_text} = '' unless defined $this->{_text};
974
9754261.71ms4261.20ms $this->addDependency();
# spent 1.20ms making 426 calls to Foswiki::Meta::addDependency, avg 3µs/call
976}
977
978=begin TML
979
980---++ ObjectMethod text([$text]) -> $text
981
982Get/set the topic body text. If $text is undef, gets the value, if it is
983defined, sets the value to that and returns the new text.
984
985Be warned - it can return undef - when a topic exists but has no topicText.
986
987=cut
988
989
# spent 9.66s (43.2ms+9.62) within Foswiki::Meta::text which was called 9189 times, avg 1.05ms/call: # 8845 times (40.5ms+4.96ms) by Foswiki::Search::formatResults at line 794 of /var/www/foswiki11/lib/Foswiki/Search.pm, avg 5µs/call # 335 times (2.63ms+9.61s) by Foswiki::Prefs::Parser::parse at line 39 of /var/www/foswiki11/lib/Foswiki/Prefs/Parser.pm, avg 28.7ms/call # 6 times (48µs+9µs) by Foswiki::__ANON__[/var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm:331] at line 232 of /var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm, avg 9µs/call # once (24µs+600ns) by Foswiki::Users::TopicUserMapping::_loadMapping at line 1688 of /var/www/foswiki11/lib/Foswiki/Users/TopicUserMapping.pm # once (13µs+1000ns) by Foswiki::UI::View::view at line 185 of /var/www/foswiki11/lib/Foswiki/UI/View.pm # once (12µs+1µs) by Foswiki::Func::readTopic at line 1596 of /var/www/foswiki11/lib/Foswiki/Func.pm
sub text {
99091893.70ms my ( $this, $val ) = @_;
99191896.32ms91895.27ms _assertIsTopic($this) if DEBUG;
# spent 5.27ms making 9189 calls to Assert::ASSERTS_OFF, avg 573ns/call
99291892.65ms if ( defined($val) ) {
993 $this->{_text} = $val;
994 }
995 else {
996
997 # Lazy load. Reload with no params will reload the _loadedRev,
998 # or load the latest if that is not defined.
99991895.14ms1029.61s $this->loadVersion() unless defined( $this->{_text} );
# spent 9.61s making 102 calls to Foswiki::Meta::loadVersion, avg 94.2ms/call
1000 }
1001918923.3ms return $this->{_text};
1002}
1003
1004=begin TML
1005
1006---++ ObjectMethod put($type, \%args)
1007
1008Put a hash of key=value pairs into the given type set in this meta. This
1009will *not* replace another value with the same name (for that see =putKeyed=)
1010
1011For example,
1012<verbatim>
1013$meta->put( 'FIELD', { name => 'MaxAge', title => 'Max Age', value =>'103' } );
1014</verbatim>
1015
1016=cut
1017
1018
# spent 17.8ms (15.8+1.92) within Foswiki::Meta::put which was called 824 times, avg 22µs/call: # 824 times (15.8ms+1.92ms) by Foswiki::Meta::_readMETA at line 3750, avg 22µs/call
sub put {
1019824498µs my ( $this, $type, $args ) = @_;
1020824786µs824744µs _assertIsTopic($this) if DEBUG;
# spent 744µs making 824 calls to Assert::ASSERTS_OFF, avg 903ns/call
1021824768µs824629µs ASSERT( defined $type ) if DEBUG;
# spent 629µs making 824 calls to Assert::ASSERTS_OFF, avg 764ns/call
1022824703µs824544µs ASSERT( defined $args && ref($args) eq 'HASH' ) if DEBUG;
# spent 544µs making 824 calls to Assert::ASSERTS_OFF, avg 660ns/call
1023
1024824577µs unless ( $this->{$type} ) {
1025824980µs $this->{$type} = [];
1026824922µs $this->{_indices}->{$type} = {};
1027 }
1028
1029824290µs my $data = $this->{$type};
1030824175µs my $i = 0;
1031824288µs if ($data) {
1032
1033 # overwrite old single value
1034824229µs if ( scalar(@$data) && defined $data->[0]->{name} ) {
1035 delete $this->{_indices}->{$type}->{ $data->[0]->{name} };
1036 }
1037824700µs $data->[0] = $args;
1038 }
1039 else {
1040 $i = push( @$data, $args ) - 1;
1041 }
10428242.13ms if ( defined $args->{name} ) {
1043 $this->{_indices}->{$type} ||= {};
1044 $this->{_indices}->{$type}->{ $args->{name} } = $i;
1045 }
1046}
1047
1048=begin TML
1049
1050---++ ObjectMethod putKeyed($type, \%args)
1051
1052Put a hash of key=value pairs into the given type set in this meta, replacing
1053any existing value with the same key.
1054
1055For example,
1056<verbatim>
1057$meta->putKeyed( 'FIELD',
1058 { name => 'MaxAge', title => 'Max Age', value =>'103' } );
1059</verbatim>
1060
1061=cut
1062
1063# Note: Array is used instead of a hash to preserve sequence
1064
1065
# spent 187ms (168+19.0) within Foswiki::Meta::putKeyed which was called 7788 times, avg 24µs/call: # 7774 times (168ms+18.9ms) by Foswiki::Meta::_readMETA at line 3744, avg 24µs/call # 14 times (171µs+32µs) by Foswiki::Compatibility::readSymmetricallyEncodedMETA at line 361 of /var/www/foswiki11/lib/Foswiki/Compatibility.pm, avg 14µs/call
sub putKeyed {
106677883.66ms my ( $this, $type, $args ) = @_;
106777886.05ms77885.51ms _assertIsTopic($this) if DEBUG;
# spent 5.51ms making 7788 calls to Assert::ASSERTS_OFF, avg 707ns/call
106877885.85ms77884.68ms ASSERT($type) if DEBUG;
# spent 4.68ms making 7788 calls to Assert::ASSERTS_OFF, avg 601ns/call
106977885.48ms77884.25ms ASSERT( $args && ref($args) eq 'HASH' ) if DEBUG;
# spent 4.25ms making 7788 calls to Assert::ASSERTS_OFF, avg 546ns/call
107077883.29ms my $keyName = $args->{name};
107177885.67ms77884.54ms ASSERT( $keyName, join( ',', keys %$args ) ) if DEBUG;
# spent 4.54ms making 7788 calls to Assert::ASSERTS_OFF, avg 583ns/call
1072
107377882.82ms unless ( $this->{$type} ) {
1074885867µs $this->{$type} = [];
1075885668µs $this->{_indices}->{$type} = {};
1076 }
1077
107877881.79ms my $data = $this->{$type};
1079
1080 # The \% shouldn't be necessary, but it is
108177884.29ms my $indices = \%{ $this->{_indices}->{$type} };
1082778849.1ms if ( defined $indices->{$keyName} ) {
1083 $data->[ $indices->{$keyName} ] = $args;
1084 }
1085 else {
108677888.40ms $indices->{$keyName} = push( @$data, $args ) - 1;
1087 }
1088}
1089
1090=begin TML
1091
1092---++ ObjectMethod putAll
1093
1094Replaces all the items of a given key with a new array.
1095
1096For example,
1097<verbatim>
1098$meta->putAll( 'FIELD',
1099 { name => 'MinAge', title => 'Min Age', value =>'50' },
1100 { name => 'MaxAge', title => 'Max Age', value =>'103' },
1101 { name => 'HairColour', title => 'Hair Colour', value =>'white' }
1102 );
1103</verbatim>
1104
1105=cut
1106
1107sub putAll {
1108 my ( $this, $type, @array ) = @_;
1109 _assertIsTopic($this) if DEBUG;
1110
1111 my %indices;
1112 for ( my $i = 0 ; $i < scalar(@array) ; $i++ ) {
1113 if ( defined $array[$i]->{name} ) {
1114 $indices{ $array[$i]->{name} } = $i;
1115 }
1116 }
1117 $this->{$type} = \@array;
1118 $this->{_indices}->{$type} = \%indices;
1119}
1120
1121=begin TML
1122
1123---++ ObjectMethod get( $type, $key ) -> \%hash
1124
1125Find the value of a meta-datum in the map. If the type is
1126keyed (identified by a =name=), the =$key= parameter is required
1127to say _which_ entry you want. Otherwise you will just get the first value.
1128
1129If you want all the keys of a given type use the 'find' method.
1130
1131The result is a reference to the hash for the item.
1132
1133For example,
1134<verbatim>
1135my $ma = $meta->get( 'FIELD', 'MinAge' );
1136my $topicinfo = $meta->get( 'TOPICINFO' ); # get the TOPICINFO hash
1137</verbatim>
1138
1139=cut
1140
1141
# spent 187ms (168+19.3) within Foswiki::Meta::get which was called 19042 times, avg 10µs/call: # 8760 times (83.8ms+11.2ms) by Foswiki::Store::QueryAlgorithms::BruteForce::getField at line 306 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 11µs/call # 8760 times (75.5ms+6.85ms) by Foswiki::Store::QueryAlgorithms::BruteForce::getField at line 323 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 9µs/call # 426 times (2.93ms+441µs) by Foswiki::Store::VC::Store::readTopic at line 110 of /var/www/foswiki11/lib/Foswiki/Store/VC/Store.pm, avg 8µs/call # 426 times (2.48ms+333µs) by Foswiki::Meta::setEmbeddedStoreForm at line 3589, avg 7µs/call # 335 times (1.77ms+233µs) by Foswiki::Prefs::Parser::parse at line 78 of /var/www/foswiki11/lib/Foswiki/Prefs/Parser.pm, avg 6µs/call # 308 times (1.34ms+176µs) by Foswiki::Meta::getRevisionInfo at line 1379, avg 5µs/call # 22 times (160µs+23µs) by Foswiki::Store::VC::Store::readTopic at line 99 of /var/www/foswiki11/lib/Foswiki/Store/VC/Store.pm, avg 8µs/call # 2 times (10µs+1µs) by Foswiki::Meta::getEmbeddedStoreForm at line 3487, avg 6µs/call # once (11µs+1µs) by Foswiki::Render::renderParent at line 172 of /var/www/foswiki11/lib/Foswiki/Render.pm # once (6µs+1µs) by Foswiki::Plugins::AutoViewTemplatePlugin::initPlugin at line 61 of /var/www/foswiki11/lib/Foswiki/Plugins/AutoViewTemplatePlugin.pm # once (4µs+500ns) by Foswiki::Meta::getFormName at line 1589
sub get {
11421904211.9ms my ( $this, $type, $name ) = @_;
11431904220.4ms1904219.3ms _assertIsTopic($this) if DEBUG;
# spent 19.3ms making 19042 calls to Assert::ASSERTS_OFF, avg 1µs/call
1144
11451904227.4ms my $data = $this->{$type};
1146190424.51ms if ($data) {
1147189204.26ms if ( defined $name ) {
1148876010.5ms my $indices = $this->{_indices}->{$type};
114987601.74ms return undef unless defined $indices;
115087607.05ms return undef unless defined $indices->{$name};
1151876044.4ms return $data->[ $indices->{$name} ];
1152 }
1153 else {
11541016035.2ms return $data->[0];
1155 }
1156 }
1157
1158122410µs return undef;
1159}
1160
1161=begin TML
1162
1163---++ ObjectMethod find ( $type ) -> @values
1164
1165Get all meta data for a specific type.
1166Returns the array stored for the type. This will be zero length
1167if there are no entries.
1168
1169For example,
1170<verbatim>
1171my $attachments = $meta->find( 'FILEATTACHMENT' );
1172</verbatim>
1173
1174=cut
1175
1176
# spent 4.28ms (3.84+436µs) within Foswiki::Meta::find which was called 555 times, avg 8µs/call: # 335 times (2.19ms+311µs) by Foswiki::Prefs::Parser::parse at line 68 of /var/www/foswiki11/lib/Foswiki/Prefs/Parser.pm, avg 7µs/call # 219 times (1.64ms+124µs) by Foswiki::Prefs::Parser::parse at line 80 of /var/www/foswiki11/lib/Foswiki/Prefs/Parser.pm, avg 8µs/call # once (12µs+1µs) by Foswiki::Attach::renderMetaData at line 69 of /var/www/foswiki11/lib/Foswiki/Attach.pm
sub find {
1177555289µs my ( $this, $type ) = @_;
1178555482µs555436µs _assertIsTopic($this) if DEBUG;
# spent 436µs making 555 calls to Assert::ASSERTS_OFF, avg 785ns/call
1179
1180555234µs my $itemsr = $this->{$type};
1181555338µs my @items = ();
1182
1183555507µs if ($itemsr) {
1184 @items = @$itemsr;
1185 }
1186
11875551.72ms return @items;
1188}
1189
1190=begin TML
1191
1192---++ ObjectMethod remove($type, $key)
1193
1194With no type, will remove all the meta-data in the object.
1195
1196With a $type but no $key, will remove _all_ items of that type
1197(so for example if $type were FILEATTACHMENT it would remove all of them)
1198
1199With a $type and a $key it will remove only the specific item.
1200
1201=cut
1202
1203sub remove {
1204 my ( $this, $type, $name ) = @_;
1205 _assertIsTopic($this) if DEBUG;
1206
1207 if ($type) {
1208 my $data = $this->{$type};
1209 return unless defined $data;
1210 if ($name) {
1211 my $indices = $this->{_indices}->{$type};
1212 if ( defined $indices ) {
1213 my $i = $indices->{$name};
1214 return unless defined $i;
1215 splice( @$data, $i, 1 );
1216 delete $indices->{$name};
1217 for ( my $i = 0 ; $i < scalar(@$data) ; $i++ ) {
1218 my $item = $data->[$i];
1219 next unless exists $item->{name};
1220 $indices->{ $item->{name} } = $i;
1221 }
1222 }
1223 }
1224 else {
1225 delete $this->{$type};
1226 delete $this->{_indices}->{$type};
1227 }
1228 }
1229 else {
1230 foreach my $entry ( keys %$this ) {
1231 unless ( $entry =~ /^_/ ) {
1232 delete $this->{$entry};
1233 }
1234 }
1235 $this->{_indices} = {};
1236 }
1237}
1238
1239=begin TML
1240
1241---++ ObjectMethod copyFrom( $otherMeta [, $type [, $nameFilter]] )
1242
1243Copy all entries of a type from another meta data set. This
1244will destroy the old values for that type, unless the
1245copied object doesn't contain entries for that type, in which
1246case it will retain the old values.
1247
1248If $type is undef, will copy ALL TYPES.
1249
1250If $nameFilter is defined (a perl regular expression), it will copy
1251only data where ={name}= matches $nameFilter.
1252
1253Does *not* copy web, topic or text.
1254
1255=cut
1256
1257sub copyFrom {
1258 my ( $this, $other, $type, $filter ) = @_;
1259 _assertIsTopic($this) if DEBUG;
1260 _assertIsTopic($other) if DEBUG;
1261
1262 if ($type) {
1263 return if $type =~ /^_/;
1264 my @data;
1265 foreach my $item ( @{ $other->{$type} } ) {
1266 if ( !$filter
1267 || ( $item->{name} && $item->{name} =~ /$filter/ ) )
1268 {
1269 my %datum = %$item;
1270 push( @data, \%datum );
1271 }
1272 }
1273 $this->putAll( $type, @data );
1274 }
1275 else {
1276 foreach my $k ( keys %$other ) {
1277 unless ( $k =~ /^_/ ) {
1278 $this->copyFrom( $other, $k );
1279 }
1280 }
1281 }
1282}
1283
1284=begin TML
1285
1286---++ ObjectMethod count($type) -> $integer
1287
1288Return the number of entries of the given type
1289
1290=cut
1291
1292
# spent 2.66ms (2.32+345µs) within Foswiki::Meta::count which was called 426 times, avg 6µs/call: # 426 times (2.32ms+345µs) by Foswiki::Meta::setEmbeddedStoreForm at line 3640, avg 6µs/call
sub count {
1293426264µs my ( $this, $type ) = @_;
1294426383µs426345µs _assertIsTopic($this) if DEBUG;
# spent 345µs making 426 calls to Assert::ASSERTS_OFF, avg 809ns/call
1295426210µs my $data = $this->{$type};
1296
12974261.09ms return scalar @$data if ( defined($data) );
1298
129915µs return 0;
1300}
1301
1302=begin TML
1303
1304---++ ObjectMethod setRevisionInfo( %opts )
1305
1306Set TOPICINFO information on the object, as specified by the parameters.
1307 * =version= - the revision number
1308 * =time= - the time stamp
1309 * =author= - the user id (cUID)
1310 * + additional data fields to save e.g. reprev, comment
1311
1312=cut
1313
1314sub setRevisionInfo {
1315 my ( $this, %data ) = @_;
1316 _assertIsTopic($this) if DEBUG;
1317
1318 my $ti = $this->get('TOPICINFO') || {};
1319
1320 foreach my $k ( keys %data ) {
1321 $ti->{$k} = $data{$k};
1322 }
1323
1324 # compatibility; older versions of the code use
1325 # RCS rev numbers. Save with them so old code can
1326 # read these topics
1327 $ti->{version} = 1 if $ti->{version} < 1;
1328 $ti->{version} = $ti->{version};
1329 $ti->{format} = $EMBEDDING_FORMAT_VERSION;
1330
1331 $this->put( 'TOPICINFO', $ti );
1332}
1333
1334=begin TML
1335
1336---++ ObjectMethod getRevisionInfo() -> \%info
1337
1338Return revision info for the loaded revision in %info with at least:
1339 * ={date}= in epochSec
1340 * ={author}= canonical user ID
1341 * ={version}= the revision number
1342
1343---++ ObjectMethod getRevisionInfo() -> ( $revDate, $author, $rev, $comment )
1344
1345Limited backwards compatibility for plugins that assume the 1.0.x interface
1346The comment is *always* blank
1347
1348=cut
1349
1350
# spent 4.53ms (2.80+1.73) within Foswiki::Meta::getRevisionInfo which was called 308 times, avg 15µs/call: # 305 times (2.76ms+1.71ms) by Foswiki::MetaCache::get at line 191 of /var/www/foswiki11/lib/Foswiki/MetaCache.pm, avg 15µs/call # 3 times (47µs+20µs) by Foswiki::Render::renderRevisionInfo at line 1752 of /var/www/foswiki11/lib/Foswiki/Render.pm, avg 22µs/call
sub getRevisionInfo {
135130883µs my $this = shift;
1352308263µs308210µs _assertIsTopic($this) if DEBUG;
# spent 210µs making 308 calls to Assert::ASSERTS_OFF, avg 683ns/call
1353
135430841µs my $info;
1355308109µs if ( not defined( $this->{_loadedRev} )
1356 and not Foswiki::Func::topicExists( $this->{_web}, $this->{_topic} ) )
1357 {
1358
1359#print STDERR "topic does not exist - at least, _loadedRev is not set..(".$this->{_web} .' '. $this->{_topic}.")\n";
1360#this does not exist on disk - no reason to goto the store for the defaults
1361#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
1362 $info = {
1363 date => 0,
1364 author => $Foswiki::Users::BaseUserMapping::DEFAULT_USER_CUID,
1365 version => 0,
1366 format => $EMBEDDING_FORMAT_VERSION,
1367 };
1368 return $info;
1369 }
1370
1371 # This used to try and get revision info from the meta
1372 # information and only kick down to the Store module for the
1373 # same information if it was not present. However there have
1374 # been several cases where the meta information in the cache
1375 # is badly out of step with the store, and the conclusion is
1376 # that it can't be trusted. For this reason, when meta is read
1377 # TOPICINFO version field is automatically undefined, which
1378 # forces this function to re-get it from the store.
1379308349µs3081.52ms my $topicinfo = $this->get('TOPICINFO');
# spent 1.52ms making 308 calls to Foswiki::Meta::get, avg 5µs/call
1380
1381308808µs if ( $topicinfo && defined $topicinfo->{version} ) {
1382 $info = {
1383 date => $topicinfo->{date},
1384 author => $topicinfo->{author},
1385 version => $topicinfo->{version},
1386 };
1387 }
1388 else {
1389
1390 # Delegate to the store
1391 $info = $this->{_session}->{store}->getVersionInfo($this);
1392
1393 # cache the result
1394 $this->setRevisionInfo(%$info);
1395 }
139630891µs if (wantarray) {
1397
1398 # Backwards compatibility for 1.0.x plugins
1399 return ( $info->{date}, $info->{author}, $info->{version}, '' );
1400 }
1401 else {
1402308681µs return $info;
1403 }
1404}
1405
1406# Determines, and caches, the topic revision info of the base version,
1407# SMELL: this is a horrid little legacy of the InfoCache object, and
1408# should be done away with.
1409sub getRev1Info {
1410 my ( $this, $attr ) = @_;
1411 _assertIsTopic($this) if DEBUG;
1412
1413#my ( $web, $topic ) = Foswiki::Func::normalizeWebTopicName( $this->{_defaultWeb}, $webtopic );
1414 my $web = $this->web;
1415 my $topic = $this->topic;
1416
1417 if ( !defined( $this->{_getRev1Info} ) ) {
1418 $this->{_getRev1Info} = {};
1419 }
1420 my $info = $this->{_getRev1Info};
1421 unless ( defined $info->{$attr} ) {
1422 my $ri = $info->{rev1info};
1423 unless ($ri) {
1424 my $tmp = Foswiki::Meta->load( $this->{_session}, $web, $topic, 1 );
1425 $info->{rev1info} = $ri = $tmp->getRevisionInfo();
1426 }
1427
1428 if ( $attr eq 'createusername' ) {
1429 $info->{createusername} =
1430 $this->{_session}->{users}->getLoginName( $ri->{author} );
1431 }
1432 elsif ( $attr eq 'createwikiname' ) {
1433 $info->{createwikiname} =
1434 $this->{_session}->{users}->getWikiName( $ri->{author} );
1435 }
1436 elsif ( $attr eq 'createwikiusername' ) {
1437 $info->{createwikiusername} =
1438 $this->{_session}->{users}->webDotWikiName( $ri->{author} );
1439 }
1440 elsif ($attr eq 'createdate'
1441 or $attr eq 'createlongdate'
1442 or $attr eq 'created' )
1443 {
1444 $info->{created} = $ri->{date};
1445
1446 # Don't pass Foswiki::Time an undef value
1447 if ( defined $ri->{date} ) {
1448 require Foswiki::Time;
1449 $info->{createdate} = Foswiki::Time::formatTime( $ri->{date} );
1450
1451 #TODO: wow thats disgusting.
1452 $info->{created} = $info->{createlongdate} =
1453 $info->{createdate};
1454 }
1455 }
1456 }
1457 return $info->{$attr};
1458}
1459
1460=begin TML
1461
1462---++ ObjectMethod merge( $otherMeta, $formDef )
1463
1464 * =$otherMeta= - a block of meta-data to merge with $this
1465 * =$formDef= reference to a Foswiki::Form that gives the types of the fields in $this
1466
1467Merge the data in the other meta block.
1468 * File attachments that only appear in one set are preserved.
1469 * Form fields that only appear in one set are preserved.
1470 * Form field values that are different in each set are text-merged
1471 * We don't merge for field attributes or title
1472 * Topic info is not touched
1473 * 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.
1474
1475=cut
1476
1477sub merge {
1478 my ( $this, $other, $formDef ) = @_;
1479 _assertIsTopic($this) if DEBUG;
1480 _assertIsTopic($other) if DEBUG;
1481
1482 my $data = $other->{FIELD};
1483 if ($data) {
1484 foreach my $otherD (@$data) {
1485 my $thisD = $this->get( 'FIELD', $otherD->{name} );
1486 if ( $thisD && $thisD->{value} ne $otherD->{value} ) {
1487 if ( $formDef->isTextMergeable( $thisD->{name} ) ) {
1488 require Foswiki::Merge;
1489 my $merged = Foswiki::Merge::merge2(
1490 'A',
1491 $otherD->{value},
1492 'B',
1493 $thisD->{value},
1494 '.*?\s+',
1495 $this->{_session},
1496 $formDef->getField( $thisD->{name} )
1497 );
1498
1499 # SMELL: we don't merge attributes or title
1500 $thisD->{value} = $merged;
1501 }
1502 }
1503 elsif ( !$thisD ) {
1504 $this->putKeyed( 'FIELD', $otherD );
1505 }
1506 }
1507 }
1508
1509 $data = $other->{FILEATTACHMENT};
1510 if ($data) {
1511 foreach my $otherD (@$data) {
1512 my $thisD = $this->get( 'FILEATTACHMENT', $otherD->{name} );
1513 if ( !$thisD ) {
1514 $this->putKeyed( 'FILEATTACHMENT', $otherD );
1515 }
1516 }
1517 }
1518}
1519
1520=begin TML
1521
1522---++ ObjectMethod forEachSelectedValue( $types, $keys, \&fn, \%options )
1523
1524Iterate over the values selected by the regular expressions in $types and
1525$keys.
1526 * =$types= - regular expression matching the names of fields to be processed. Will default to qr/^[A-Z]+$/ if undef.
1527 * =$keys= - regular expression matching the names of keys to be processed. Will default to qr/^[a-z]+$/ if undef.
1528
1529Iterates over each value, calling =\&fn= on each, and replacing the value
1530with the result of \&fn.
1531
1532\%options will be passed on to $fn, with the following additions:
1533 * =_type= => the type name (e.g. "FILEATTACHMENT")
1534 * =_key= => the key name (e.g. "user")
1535
1536=cut
1537
1538sub forEachSelectedValue {
1539 my ( $this, $types, $keys, $fn, $options ) = @_;
1540 _assertIsTopic($this) if DEBUG;
1541
1542 $types ||= qr/^[A-Z]+$/;
1543 $keys ||= qr/^[a-z]+$/;
1544
1545 foreach my $type ( grep { /$types/ } keys %$this ) {
1546 $options->{_type} = $type;
1547 my $data = $this->{$type};
1548 next unless $data;
1549 foreach my $datum (@$data) {
1550 foreach my $key ( grep { /$keys/ } keys %$datum ) {
1551 $options->{_key} = $key;
1552 $datum->{$key} = &$fn( $datum->{$key}, $options );
1553 }
1554 }
1555 }
1556}
1557
1558=begin TML
1559
1560---++ ObjectMethod getParent() -> $parent
1561
1562Gets the TOPICPARENT name.
1563
1564=cut
1565
1566sub getParent {
1567 my ($this) = @_;
1568
1569 my $value = '';
1570 my $parent = $this->get('TOPICPARENT');
1571 $value = $parent->{name} if ($parent);
1572
1573 # Return empty string (not undef), if TOPICPARENT meta is broken
1574 $value = '' if ( !defined $value );
1575 return $value;
1576}
1577
1578=begin TML
1579
1580---++ ObjectMethod getFormName() -> $formname
1581
1582Returns the name of the FORM, or '' if none.
1583
1584=cut
1585
1586
# spent 13µs (8+5) within Foswiki::Meta::getFormName which was called: # once (8µs+5µs) by Foswiki::Meta::renderFormForDisplay at line 1608
sub getFormName {
15871600ns my ($this) = @_;
1588
158911µs15µs my $aForm = $this->get('FORM');
# spent 5µs making 1 call to Foswiki::Meta::get
15901600ns if ($aForm) {
1591 return $aForm->{name};
1592 }
159315µs return '';
1594}
1595
1596=begin TML
1597
1598---++ ObjectMethod renderFormForDisplay() -> $html
1599
1600Render the form contained in the meta for display.
1601
1602=cut
1603
1604
# spent 4.91ms (2.56+2.35) within Foswiki::Meta::renderFormForDisplay which was called: # once (2.56ms+2.35ms) by Foswiki::META at line 19 of /var/www/foswiki11/lib/Foswiki/Macros/META.pm
sub renderFormForDisplay {
16051800ns my ($this) = @_;
16061700ns1500ns _assertIsTopic($this) if DEBUG;
# spent 500ns making 1 call to Assert::ASSERTS_OFF
1607
160813µs113µs my $fname = $this->getFormName();
# spent 13µs making 1 call to Foswiki::Meta::getFormName
1609
16101112µs require Foswiki::Form;
161111µs require Foswiki::OopsException;
161218µs return '' unless $fname;
1613
1614 my $form;
1615 my $result;
1616 try {
1617 $form = new Foswiki::Form( $this->{_session}, $this->{_web}, $fname );
1618 $result = $form->renderForDisplay($this);
1619 }
1620 catch Foswiki::OopsException with {
1621
1622 # Make pseudo-form from field data
1623 $form =
1624 new Foswiki::Form( $this->{_session}, $this->{_web}, $fname, $this );
1625 $result =
1626 $this->{_session}->inlineAlert( 'alerts', 'formdef_missing', $fname );
1627 $result .= $form->renderForDisplay($this) if $form;
1628 };
1629
1630 return $result;
1631}
1632
1633=begin TML
1634
1635---++ ObjectMethod renderFormFieldForDisplay($name, $format, $attrs) -> $text
1636
1637Render a single formfield, using the $format. See
1638Foswiki::Form::FormField::renderForDisplay for a description of how the value
1639is rendered.
1640
1641=cut
1642
1643sub renderFormFieldForDisplay {
1644 my ( $this, $name, $format, $attrs ) = @_;
1645 _assertIsTopic($this) if DEBUG;
1646
1647 my $value;
1648 my $mf = $this->get( 'FIELD', $name );
1649 unless ($mf) {
1650
1651 # Not a valid field name, maybe it's a title.
1652 require Foswiki::Form;
1653 $name = Foswiki::Form::fieldTitle2FieldName($name);
1654 $mf = $this->get( 'FIELD', $name );
1655 }
1656 return '' unless $mf; # field not found
1657
1658 $value = $mf->{value};
1659
1660 # remove nop exclamation marks from form field value before it is put
1661 # inside a format like [[$topic][$formfield()]] that prevents it being
1662 # detected
1663 $value =~ s/!(\w+)/<nop>$1/gos;
1664
1665 my $fname = $this->getFormName();
1666 if ($fname) {
1667 require Foswiki::Form;
1668 my $result;
1669 try {
1670 my $form =
1671 new Foswiki::Form( $this->{_session}, $this->{_web}, $fname );
1672 my $field = $form->getField($name);
1673 if ($field) {
1674 $result = $field->renderForDisplay( $format, $value, $attrs );
1675 }
1676 }
1677 catch Foswiki::OopsException with {
1678
1679 # Form not found, ignore
1680 };
1681
1682 return $result if defined $result;
1683 }
1684
1685 # Form or field wasn't found, do your best!
1686 my $f = $this->get( 'FIELD', $name );
1687 if ($f) {
1688 $format =~ s/\$title/$f->{title}/;
1689 require Foswiki::Render;
1690 $value = Foswiki::Render::protectFormFieldValue( $value, $attrs );
1691 $format =~ s/\$value/$value/;
1692 }
1693 return $format;
1694}
1695
1696# Enable this for debug. Done as a sub to allow perl to optimise it out.
16978181.82ms
# spent 870µs within Foswiki::Meta::MONITOR_ACLS which was called 818 times, avg 1µs/call: # 409 times (436µs+0s) by Foswiki::Meta::haveAccess at line 1743, avg 1µs/call # 319 times (351µs+0s) by Foswiki::Meta::haveAccess at line 1844, avg 1µs/call # 86 times (78µs+0s) by Foswiki::Meta::haveAccess at line 1748, avg 907ns/call # 3 times (3µs+0s) by Foswiki::Meta::haveAccess at line 1813, avg 1µs/call # once (1µs+0s) by Foswiki::Meta::haveAccess at line 1773
sub MONITOR_ACLS { 0 }
1698
1699# Get an ACL preference. Returns a reference to a list of cUIDs, or undef.
1700# If the preference is defined but is empty, then a reference to an
1701# empty list is returned.
1702# This function canonicalises the parsing of a users list. Is this the right
1703# place for it?
1704
# spent 363ms (13.8+350) within Foswiki::Meta::_getACL which was called 1104 times, avg 329µs/call: # 322 times (5.01ms+301ms) by Foswiki::Meta::haveAccess at line 1796, avg 950µs/call # 322 times (1.95ms+7.99ms) by Foswiki::Meta::haveAccess at line 1808, avg 31µs/call # 230 times (6.10ms+39.7ms) by Foswiki::Meta::haveAccess at line 1757, avg 199µs/call # 230 times (688µs+1.00ms) by Foswiki::Meta::haveAccess at line 1758, avg 7µs/call
sub _getACL {
17051104535µs my ( $this, $mode ) = @_;
170611041.36ms1104350ms my $text = $this->getPreference($mode);
# spent 350ms making 1104 calls to Foswiki::Meta::getPreference, avg 317µs/call
1707110415.4ms return undef unless defined $text;
1708
1709 # Remove HTML tags (compatibility, inherited from Users.pm
1710645384µs $text =~ s/(<[^>]*>)//g;
1711
1712 # Dump the users web specifier if userweb
17137481.31ms my @list = grep { /\S/ } map {
17146452.01ms s/^($Foswiki::cfg{UsersWebName}|%USERSWEB%|%MAINWEB%)\.//;
1715374196µs $_
1716 } split( /[,\s]+/, $text );
17176451.85ms return \@list;
1718}
1719
1720=begin TML
1721
1722---++ ObjectMethod haveAccess($mode, $cUID) -> $boolean
1723
1724 * =$mode= - 'VIEW', 'CHANGE', 'CREATE', etc. (defaults to VIEW)
1725 * =$cUID= - Canonical user id (defaults to current user)
1726Check if the user has the given mode of access to the topic. This call
1727may result in the topic being read.
1728
1729=cut
1730
1731
# spent 712ms (31.9+680) within Foswiki::Meta::haveAccess which was called 409 times, avg 1.74ms/call: # 305 times (13.4ms+92.1ms) by Foswiki::MetaCache::get at line 198 of /var/www/foswiki11/lib/Foswiki/MetaCache.pm, avg 346µs/call # 93 times (3.35ms+292ms) by Foswiki::WebFilter::ok at line 47 of /var/www/foswiki11/lib/Foswiki/WebFilter.pm, avg 3.18ms/call # 6 times (14.8ms+291ms) by Foswiki::INCLUDE at line 207 of /var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm, avg 51.0ms/call # 3 times (154µs+3.04ms) by Foswiki::REVINFO at line 32 of /var/www/foswiki11/lib/Foswiki/Macros/REVINFO.pm, avg 1.06ms/call # once (47µs+885µs) by Foswiki::UI::checkAccess at line 538 of /var/www/foswiki11/lib/Foswiki/UI.pm # once (46µs+319µs) by Foswiki::Func::checkAccessPermission at line 1436 of /var/www/foswiki11/lib/Foswiki/Func.pm
sub haveAccess {
1732409299µs my ( $this, $mode, $cUID ) = @_;
173340981µs $mode ||= 'VIEW';
1734409343µs $cUID ||= $this->{_session}->{user};
1735409236µs31.41ms if ( defined $this->{_topic} && !defined $this->{_loadedRev} ) {
# spent 1.41ms making 3 calls to Foswiki::Meta::loadVersion, avg 468µs/call
1736
1737 # Lazy load the latest version.
1738 $this->loadVersion();
1739 }
1740409168µs my $session = $this->{_session};
1741409141µs undef $reason;
1742
1743409502µs409436µs print STDERR "Check $mode access $cUID to " . $this->getPath() . "\n"
# spent 436µs making 409 calls to Foswiki::Meta::MONITOR_ACLS, avg 1µs/call
1744 if MONITOR_ACLS;
1745
1746 # super admin is always allowed
1747409755µs4091.06ms if ( $session->{users}->isAdmin($cUID) ) {
# spent 1.06ms making 409 calls to Foswiki::Users::isAdmin, avg 3µs/call
174886106µs8678µs print STDERR "$cUID - ADMIN\n" if MONITOR_ACLS;
# spent 78µs making 86 calls to Foswiki::Meta::MONITOR_ACLS, avg 907ns/call
174986246µs return 1;
1750 }
1751
1752323189µs $mode = uc($mode);
1753
175432396µs my ( $allow, $deny );
1755323222µs if ( $this->{_topic} ) {
1756
1757230339µs23045.8ms my $allow = $this->_getACL( 'ALLOWTOPIC' . $mode );
# spent 45.8ms making 230 calls to Foswiki::Meta::_getACL, avg 199µs/call
1758230340µs2301.69ms my $deny = $this->_getACL( 'DENYTOPIC' . $mode );
# spent 1.69ms making 230 calls to Foswiki::Meta::_getACL, avg 7µs/call
1759
1760 # Check DENYTOPIC
176123039µs if ( defined($deny) ) {
17621700ns if ( scalar(@$deny) != 0 ) {
1763 if ( $session->{users}->isInUserList( $cUID, $deny ) ) {
1764 $reason =
1765 $session->i18n->maketext('access denied on topic');
1766 print STDERR $reason, "\n" if MONITOR_ACLS;
1767 return 0;
1768 }
1769 }
1770 else {
1771
1772 # If DENYTOPIC is empty, don't deny _anyone_
177312µs11µs print STDERR "DENYTOPIC is empty\n" if MONITOR_ACLS;
# spent 1µs making 1 call to Foswiki::Meta::MONITOR_ACLS
177416µs return 1;
1775 }
1776 }
1777
1778 # Check ALLOWTOPIC. If this is defined the user _must_ be in it
177922929µs if ( defined($allow) && scalar(@$allow) != 0 ) {
1780 if ( $session->{users}->isInUserList( $cUID, $allow ) ) {
1781 print STDERR "in ALLOWTOPIC\n" if MONITOR_ACLS;
1782 return 1;
1783 }
1784 $reason = $session->i18n->maketext('access not allowed on topic');
1785 print STDERR $reason, "\n" if MONITOR_ACLS;
1786 return 0;
1787 }
1788229340µs2294.92ms $this = $this->getContainer(); # Web
# spent 4.92ms making 229 calls to Foswiki::Meta::getContainer, avg 21µs/call
1789 }
1790
1791322191µs if ( $this->{_web} ) {
1792
1793 # Check DENYWEB, but only if DENYTOPIC is not set (even if it
1794 # is empty - empty means "don't deny anybody")
1795322127µs unless ( defined($deny) ) {
1796322543µs322306ms $deny = $this->_getACL( 'DENYWEB' . $mode );
# spent 306ms making 322 calls to Foswiki::Meta::_getACL, avg 950µs/call
1797322854µs32267.1ms if ( defined($deny)
# spent 67.1ms making 322 calls to Foswiki::Users::isInUserList, avg 208µs/call
1798 && $session->{users}->isInUserList( $cUID, $deny ) )
1799 {
1800 $reason = $session->i18n->maketext('access denied on web');
1801 print STDERR $reason, "\n" if MONITOR_ACLS;
1802 return 0;
1803 }
1804 }
1805
1806 # Check ALLOWWEB. If this is defined and not overridden by
1807 # ALLOWTOPIC, the user _must_ be in it.
1808322641µs3229.94ms $allow = $this->_getACL( 'ALLOWWEB' . $mode );
# spent 9.94ms making 322 calls to Foswiki::Meta::_getACL, avg 31µs/call
1809
1810322198µs if ( defined($allow) && scalar(@$allow) != 0 ) {
181138µs3243ms unless ( $session->{users}->isInUserList( $cUID, $allow ) ) {
# spent 243ms making 3 calls to Foswiki::Users::isInUserList, avg 80.9ms/call
1812322µs636µs $reason = $session->i18n->maketext('access not allowed on web');
# spent 24µs making 3 calls to Foswiki::I18N::Fallback::maketext, avg 8µs/call # spent 12µs making 3 calls to Foswiki::i18n, avg 4µs/call
181335µs33µs print STDERR $reason, "\n" if MONITOR_ACLS;
# spent 3µs making 3 calls to Foswiki::Meta::MONITOR_ACLS, avg 1µs/call
1814311µs return 0;
1815 }
1816 }
1817
1818 }
1819 else {
1820
1821 # No web, we are checking at the root. Check DENYROOT and ALLOWROOT.
1822 $deny = $this->_getACL( 'DENYROOT' . $mode );
1823
1824 if ( defined($deny)
1825 && $session->{users}->isInUserList( $cUID, $deny ) )
1826 {
1827 $reason = $session->i18n->maketext('access denied on root');
1828 print STDERR $reason, "\n" if MONITOR_ACLS;
1829 return 0;
1830 }
1831
1832 $allow = $this->_getACL( 'ALLOWROOT' . $mode );
1833
1834 if ( defined($allow) && scalar(@$allow) != 0 ) {
1835 unless ( $session->{users}->isInUserList( $cUID, $allow ) ) {
1836 $reason =
1837 $session->i18n->maketext('access not allowed on root');
1838 print STDERR $reason, "\n" if MONITOR_ACLS;
1839 return 0;
1840 }
1841 }
1842 }
1843
1844319429µs319351µs if (MONITOR_ACLS) {
# spent 351µs making 319 calls to Foswiki::Meta::MONITOR_ACLS, avg 1µs/call
1845 print STDERR "OK, permitted\n";
1846 print STDERR 'ALLOW: ' . join( ',', @$allow ) . "\n" if defined $allow;
1847 print STDERR 'DENY: ' . join( ',', @$deny ) . "\n" if defined $deny;
1848 }
18493191.37ms return 1;
1850}
1851
1852=begin TML
1853
1854---++ ObjectMethod save( %options )
1855
1856Save the current object, invoking appropriate plugin handlers
1857 * =%options= - Hash of options, see saveAs for list of keys
1858
1859=cut
1860
1861# SMELL: arguably save should only be permitted if the loaded rev of
1862# the object is the same as the latest rev.
1863sub save {
1864 my $this = shift;
1865 ASSERT( scalar(@_) % 2 == 0 ) if DEBUG;
1866 my %opts = @_;
1867 _assertIsTopic($this) if DEBUG;
1868
1869 my $plugins = $this->{_session}->{plugins};
1870
1871 # make sure version and date in TOPICINFO are up-to-date
1872 # (side effect of getRevisionInfo)
1873 $this->getRevisionInfo();
1874
1875 # Semantics inherited from Cairo. See
1876 # Foswiki:Codev.BugBeforeSaveHandlerBroken
1877 if ( $plugins->haveHandlerFor('beforeSaveHandler') ) {
1878
1879 # Break up the tom and write the meta into the topic text.
1880 # Nasty compatibility requirement as some old plugins may hack the
1881 # meta instead of using the Meta API
1882 my $text = $this->getEmbeddedStoreForm();
1883
1884 my $pretext = $text; # text before the handler modifies it
1885 my $premeta = $this->stringify(); # just the meta, no text
1886 unless ( $this->{_loadedRev} ) {
1887
1888 # The meta obj doesn't have a loaded rev yet, and we have to block the
1889 # beforeSaveHandlers from loading the topic from store. We are saving,
1890 # and anything we have in $this is going to get written anyway, so we
1891 # can simply mark it as "the latest".
1892 # SMELL: this may not work if the beforeSaveHandler tries to use the
1893 # meta obj for access control checks, so that is not recommended.
1894 $this->{_loadedRev} = $this->getLatestRev();
1895 }
1896
1897 $plugins->dispatch( 'beforeSaveHandler', $text, $this->{_topic},
1898 $this->{_web}, $this );
1899
1900 # If the text has changed; it may be a text or meta change, or both
1901 if ( $text ne $pretext ) {
1902
1903 # Create a new object to parse the changed text
1904 my $after =
1905 new Foswiki::Meta( $this->{_session}, $this->{_web},
1906 $this->{_topic}, $text );
1907 unless ( $this->stringify() ne $premeta ) {
1908
1909 # Meta-data changes in the object take priority over
1910 # conflicting changes in the text. So if there have been
1911 # *any* changes in the meta, ignore changes in the text.
1912 $this->copyFrom($after);
1913 }
1914 $this->text( $after->text() );
1915 }
1916 }
1917
1918 my $signal;
1919 my $newRev;
1920 try {
1921 $newRev = $this->saveAs( $this->{_web}, $this->{_topic}, %opts );
1922 }
1923 catch Error::Simple with {
1924 $signal = shift;
1925 };
1926
1927 # Semantics inherited from TWiki. See
1928 # TWiki:Codev.BugBeforeSaveHandlerBroken
1929 if ( $plugins->haveHandlerFor('afterSaveHandler') ) {
1930 my $text = $this->getEmbeddedStoreForm();
1931 delete $this->{_preferences}; # Make sure handler has changed prefs
1932 my $error = $signal ? $signal->{-text} : undef;
1933 $plugins->dispatch( 'afterSaveHandler', $text, $this->{_topic},
1934 $this->{_web}, $error, $this );
1935 }
1936
1937 throw $signal if $signal;
1938
1939 my @extras = ();
1940 push( @extras, 'minor' ) if $opts{minor}; # don't notify
1941 push( @extras, 'dontlog' ) if $opts{dontlog}; # don't statisticify
1942
1943 $this->{_session}->logEvent(
1944 'save',
1945 $this->{_web} . '.' . $this->{_topic},
1946 join( ', ', @extras ),
1947 $this->{_session}->{user}
1948 );
1949
1950 return $newRev;
1951}
1952
1953=begin TML
1954
1955---++ ObjectMethod saveAs( $web, $topic, %options ) -> $rev
1956
1957Save the current topic to a store location. Only works on topics.
1958*without* invoking plugins handlers.
1959 * =$web.$topic= - where to move to
1960 * =%options= - Hash of options, may include:
1961 * =forcenewrevision= - force an increment in the revision number,
1962 even if content doesn't change.
1963 * =dontlog= - don't include this change in statistics
1964 * =minor= - don't notify this change
1965 * =savecmd= - Save command (core use only)
1966 * =forcedate= - force the revision date to be this (core only)
1967 * =author= - cUID of author of change (core only - default current user)
1968
1969Note that the %options are passed on verbatim from Foswiki::Func::saveTopic,
1970so an extension author can in fact use all these options. However those
1971marked "core only" are for core use only and should *not* be used in
1972extensions.
1973
1974Returns the saved revision number.
1975
1976=cut
1977
1978# SMELL: arguably save should only be permitted if the loaded rev
1979# of the object is the same as the latest rev.
1980sub saveAs {
1981 my $this = shift;
1982 _assertIsTopic($this) if DEBUG;
1983 my $newWeb = shift;
1984 my $newTopic = shift;
1985 ASSERT( scalar(@_) % 2 == 0 ) if DEBUG;
1986 my %opts = @_;
1987 my $cUID = $opts{author} || $this->{_session}->{user};
1988 $this->{_web} = $newWeb if $newWeb;
1989 $this->{_topic} = $newTopic if $newTopic;
1990 _assertIsTopic($this) if DEBUG;
1991
1992 unless ( $this->{_topic} eq $Foswiki::cfg{WebPrefsTopicName} ) {
1993
1994 # Don't verify web existance for WebPreferences, as saving
1995 # WebPreferences creates the web.
1996 unless ( $this->{_session}->{store}->webExists( $this->{_web} ) ) {
1997 throw Error::Simple( 'Unable to save topic '
1998 . $this->{_topic}
1999 . ' - web '
2000 . $this->{_web}
2001 . ' does not exist' );
2002 }
2003 }
2004
2005 $this->_atomicLock($cUID);
2006 my $i = $this->{_session}->{store}->getRevisionHistory($this);
2007 my $currentRev = $i->hasNext() ? $i->next() : 1;
2008 try {
2009 if ( $currentRev && !$opts{forcenewrevision} ) {
2010
2011 # See if we want to replace the existing top revision
2012 my $mtime1 =
2013 $this->{_session}->{store}
2014 ->getApproxRevTime( $this->{_web}, $this->{_topic} );
2015 my $mtime2 = time();
2016 my $dt = abs( $mtime2 - $mtime1 );
2017 if ( $dt < $Foswiki::cfg{ReplaceIfEditedAgainWithin} ) {
2018 my $info = $this->{_session}->{store}->getVersionInfo($this);
2019
2020 # same user?
2021 if ( $info->{author} eq $cUID ) {
2022
2023 # reprev is required so we can tell when a merge is
2024 # based on something that is *not* the original rev
2025 # where another users' edit started.
2026 $info->{reprev} = $info->{version};
2027 $info->{date} = $opts{forcedate} || time();
2028 $this->setRevisionInfo(%$info);
2029 $this->{_session}->{store}->repRev( $this, $cUID, %opts );
2030 $this->{_loadedRev} = $currentRev;
2031 return $currentRev;
2032 }
2033 }
2034 }
2035 my $nextRev = $this->{_session}->{store}->getNextRevision($this);
2036 $this->setRevisionInfo(
2037 date => $opts{forcedate} || time(),
2038 author => $cUID,
2039 version => $nextRev,
2040 );
2041
2042 my $checkSave =
2043 $this->{_session}->{store}->saveTopic( $this, $cUID, \%opts );
2044 ASSERT( $checkSave == $nextRev, "$checkSave != $nextRev" ) if DEBUG;
2045 $this->{_loadedRev} = $nextRev;
2046 }
2047 finally {
2048 $this->_atomicUnlock($cUID);
2049 $this->fireDependency();
2050 };
2051 return $this->{_loadedRev};
2052}
2053
2054# An atomic lock will cause other
2055# processes that also try to claim a lock to block. A lock has a
2056# maximum lifetime of 2 minutes, so operations on a locked topic
2057# must be completed within that time. You cannot rely on the
2058# lock timeout clearing the lock, though; that should always
2059# be done by calling _atomicUnlock. The best thing to do is to guard
2060# the locked section with a try..finally clause. See man Error for more info.
2061#
2062# Atomic locks are _not_ the locks used when a topic is edited; those are
2063# Leases.
2064
2065sub _atomicLock {
2066 my ( $this, $cUID ) = @_;
2067 if ( $this->{_topic} ) {
2068 my $logger = $this->{_session}->logger();
2069 while (1) {
2070 my ( $user, $time ) =
2071 $this->{_session}->{store}->atomicLockInfo($this);
2072 last if ( !$user || $cUID eq $user );
2073 $logger->log( 'warning',
2074 'Lock on '
2075 . $this->getPath() . ' for '
2076 . $cUID
2077 . " denied by $user" );
2078
2079 # see how old the lock is. If it's older than 2 minutes,
2080 # break it anyway. Locks are atomic, and should never be
2081 # held that long, by _any_ process.
2082 if ( time() - $time > 2 * 60 ) {
2083 $logger->log( 'warning',
2084 $cUID . " broke ${user}s lock on " . $this->getPath() );
2085 $this->{_session}->{store}->atomicUnlock( $this, $cUID );
2086 last;
2087 }
2088
2089 # wait a couple of seconds before trying again
2090 sleep(2);
2091 }
2092
2093 # Topic
2094 $this->{_session}->{store}->atomicLock( $this, $cUID );
2095 }
2096 else {
2097
2098 # Web: Recursively lock subwebs and topics
2099 my $it = $this->eachWeb();
2100 while ( $it->hasNext() ) {
2101 my $web = $this->{_web} . '/' . $it->next();
2102 my $meta = $this->new( $this->{_session}, $web );
2103 $meta->_atomicLock($cUID);
2104 }
2105 $it = $this->eachTopic();
2106 while ( $it->hasNext() ) {
2107 my $meta =
2108 $this->new( $this->{_session}, $this->{_web}, $it->next() );
2109 $meta->_atomicLock($cUID);
2110 }
2111 }
2112}
2113
2114sub _atomicUnlock {
2115 my ( $this, $cUID ) = @_;
2116 if ( $this->{_topic} ) {
2117 $this->{_session}->{store}->atomicUnlock($this);
2118 }
2119 else {
2120 my $it = $this->eachWeb();
2121 while ( $it->hasNext() ) {
2122 my $web = $this->{_web} . '/' . $it->next();
2123 my $meta = $this->new( $this->{_session}, $web );
2124 $meta->_atomicUnlock($cUID);
2125 }
2126 $it = $this->eachTopic();
2127 while ( $it->hasNext() ) {
2128 my $meta =
2129 $this->new( $this->{_session}, $this->{_web}, $it->next() );
2130 $meta->_atomicUnlock($cUID);
2131 }
2132 }
2133}
2134
2135=begin TML
2136
2137---++ ObjectMethod move($to, %opts)
2138
2139Move this object (web or topic) to a store location specified by the
2140object $to. %opts may include:
2141 * =user= - cUID of the user doing the moving.
2142
2143=cut
2144
2145# will assert false if the loaded rev of the object is not
2146# the latest rev.
2147sub move {
2148 my ( $this, $to, %opts ) = @_;
2149 ASSERT( $this->{_web}, 'this is not a movable object' ) if DEBUG;
2150 ASSERT( $to->isa('Foswiki::Meta') && $to->{_web},
2151 'to is not a moving target' )
2152 if DEBUG;
2153
2154 my $cUID = $opts{user} || $this->{_session}->{user};
2155
2156 if ( $this->{_topic} ) {
2157
2158 # Move topic
2159
2160 $this->_atomicLock($cUID);
2161 $to->_atomicLock($cUID);
2162
2163 # Ensure latest rev is loaded
2164 my $from;
2165 if ( $this->latestIsLoaded() ) {
2166 $from = $this;
2167 }
2168 else {
2169 $from = $this->load();
2170 }
2171
2172 # Clear outstanding leases. We assume that the caller has checked
2173 # that the lease is OK to kill.
2174 $from->clearLease() if $from->getLease();
2175 try {
2176 $from->put(
2177 'TOPICMOVED',
2178 {
2179 from => $from->getPath(),
2180 to => $to->getPath(),
2181 date => time(),
2182 by => $cUID,
2183 }
2184 );
2185
2186 # save the metadata change without logging
2187 $this->saveAs(
2188 $this->{_web}, $this->{_topic},
2189 dontlog => 1, # no statistics
2190 );
2191 $from->{_session}->{store}->moveTopic( $from, $to, $cUID );
2192 $to->loadVersion();
2193 }
2194 finally {
2195 $from->_atomicUnlock($cUID);
2196 $to->_atomicUnlock($cUID);
2197 $from->fireDependency();
2198 $to->fireDependency();
2199 };
2200
2201 }
2202 else {
2203
2204 # Move web
2205 ASSERT( !$this->{_session}->{store}->webExists( $to->{_web} ),
2206 "$to->{_web} does not exist" )
2207 if DEBUG;
2208 $this->_atomicLock($cUID);
2209 $this->{_session}->{store}->moveWeb( $this, $to, $cUID );
2210
2211 # No point in unlocking $this - it's moved!
2212 $to->_atomicUnlock($cUID);
2213 }
2214
2215 # Log rename
2216 my $old = $this->{_web} . '.' . ( $this->{_topic} || '' );
2217 my $new = $to->{_web} . '.' . ( $to->{_topic} || '' );
2218 $this->{_session}
2219 ->logEvent( 'rename', $old, "moved to $new", $this->{_session}->{user} );
2220
2221 # alert plugins of topic move
2222 $this->{_session}->{plugins}
2223 ->dispatch( 'afterRenameHandler', $this->{_web}, $this->{_topic} || '',
2224 '', $to->{_web}, $to->{_topic} || '', '' );
2225}
2226
2227=begin TML
2228
2229---++ ObjectMethod deleteMostRecentRevision(%opts)
2230Delete (or elide) the most recent revision of this. Only works on topics.
2231
2232=%opts= may include
2233 * =user= - cUID of user doing the unlocking
2234
2235=cut
2236
2237sub deleteMostRecentRevision {
2238 my ( $this, %opts ) = @_;
2239 _assertIsTopic($this) if DEBUG;
2240 my $rev;
2241 my $cUID = $opts{user} || $this->{_session}->{user};
2242
2243 $this->_atomicLock($cUID);
2244 try {
2245 $rev = $this->{_session}->{store}->delRev( $this, $cUID );
2246 }
2247 finally {
2248 $this->_atomicUnlock($cUID);
2249 };
2250
2251 # TODO: delete entry in .changes
2252
2253 # write log entry
2254 $this->{_session}->logEvent(
2255 'cmd',
2256 $this->{_web} . '.' . $this->{_topic},
2257 "delRev $rev by " . $this->{_session}->{user}
2258 );
2259}
2260
2261=begin TML
2262
2263---++ ObjectMethod replaceMostRecentRevision( %opts )
2264Replace the most recent revision with whatever is in the memory copy.
2265Only works on topics.
2266
2267%opts may include:
2268 * =forcedate= - try and re-use the date of the original check
2269 * =user= - cUID of the user doing the action
2270
2271=cut
2272
2273sub replaceMostRecentRevision {
2274 my $this = shift;
2275 my %opts = @_;
2276 _assertIsTopic($this) if DEBUG;
2277
2278 my $cUID = $opts{user} || $this->{_session}->{user};
2279
2280 $this->_atomicLock($cUID);
2281
2282 my $info = $this->getRevisionInfo();
2283
2284 if ( $opts{forcedate} ) {
2285
2286 # We are trying to force the rev to be saved with the same date
2287 # and user as the prior rev. However, exactly the same date may
2288 # cause some revision control systems to barf, so to avoid this we
2289 # add 1 minute to the rev time. Note that this mode of operation
2290 # will normally require sysadmin privilege, as it can result in
2291 # confused rev dates if abused.
2292 $info->{date} += 60;
2293 }
2294 else {
2295
2296 # use defaults (current time, current user)
2297 $info->{date} = time();
2298 $info->{author} = $cUID;
2299 }
2300
2301 # repRev is required so we can tell when a merge is based on something
2302 # that is *not* the original rev where another users' edit started.
2303 $info->{reprev} = $info->{version};
2304 $this->setRevisionInfo(%$info);
2305
2306 try {
2307 $this->{_session}->{store}->repRev( $this, $cUID, @_ );
2308 }
2309 finally {
2310 $this->_atomicUnlock($cUID);
2311 };
2312
2313 # write log entry
2314 require Foswiki::Time;
2315 my @extras = ( $info->{version} );
2316 push( @extras,
2317 Foswiki::Time::formatTime( $info->{date}, '$rcs', 'gmtime' ) );
2318 push( @extras, 'minor' ) if $opts{minor};
2319 push( @extras, 'dontlog' ) if $opts{dontlog};
2320 push( @extras, 'forced' ) if $opts{forcedate};
2321 $this->{_session}
2322 ->logEvent( 'reprev', $this->getPath(), join( ', ', @extras ), $cUID );
2323}
2324
2325=begin TML
2326
2327---++ ObjectMethod getRevisionHistory([$attachment]) -> $iterator
2328
2329Get an iterator over the range of version identifiers (just the identifiers,
2330not the content).
2331
2332$attachment is optional.
2333
2334Not valid on webs. Returns a null iterator if no revisions exist.
2335
2336=cut
2337
2338
# spent 394ms (38µs+394) within Foswiki::Meta::getRevisionHistory which was called 2 times, avg 197ms/call: # once (25µs+220ms) by Foswiki::UI::View::view at line 128 of /var/www/foswiki11/lib/Foswiki/UI/View.pm # once (13µs+175ms) by Foswiki::UI::View::revisionsAround at line 440 of /var/www/foswiki11/lib/Foswiki/UI/View.pm
sub getRevisionHistory {
233922µs my ( $this, $attachment ) = @_;
234023µs22µs _assertIsTopic($this) if DEBUG;
# spent 2µs making 2 calls to Assert::ASSERTS_OFF, avg 900ns/call
2341236µs2394ms return $this->{_session}->{store}->getRevisionHistory( $this, $attachment );
# spent 394ms making 2 calls to Foswiki::Store::VC::Store::getRevisionHistory, avg 197ms/call
2342}
2343
2344=begin TML
2345
2346---++ ObjectMethod getLatestRev[$attachment]) -> $revision
2347
2348Get the revision ID of the latest revision.
2349
2350$attachment is optional.
2351
2352Not valid on webs.
2353
2354=cut
2355
2356sub getLatestRev {
2357 my $this = shift;
2358 my $it = $this->getRevisionHistory(@_);
2359 return 0 unless $it->hasNext();
2360 return $it->next();
2361}
2362
2363=begin TML
2364
2365---++ ObjectMethod latestIsLoaded() -> $boolean
2366Return true if the currently loaded rev is the latest rev. Note that there may have
2367been changes to the meta or text locally in the loaded meta; these changes will be
2368retained.
2369
2370Only valid on topics.
2371
2372=cut
2373
2374
# spent 54.6ms (48.3+6.24) within Foswiki::Meta::latestIsLoaded which was called 8761 times, avg 6µs/call: # 8760 times (48.3ms+6.24ms) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 239 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 6µs/call # once (15µs+2µs) by Foswiki::QUERY at line 21 of /var/www/foswiki11/lib/Foswiki/Macros/QUERY.pm
sub latestIsLoaded {
237587612.06ms my $this = shift;
237687618.15ms87616.24ms _assertIsTopic($this) if DEBUG;
# spent 6.24ms making 8761 calls to Assert::ASSERTS_OFF, avg 713ns/call
2377876127.3ms return $this->{_latestIsLoaded} if defined $this->{_latestIsLoaded};
237817µs return defined $this->{_loadedRev}
2379 && $this->{_loadedRev} == $this->getLatestRev();
2380}
2381
2382=begin TML
2383
2384---++ ObjectMethod getLoadedRev() -> $integer
2385
2386Get the currently loaded revision. Result will be a revision number, or
2387undef if no revision has been loaded. Only valid on topics.
2388
2389WARNING: some store implementations use the concept of a "working copy" of
2390each topic that may be modified *without* being added to the revision
2391control system. This means that the version number reported for the latest
2392rev may not be the actual latest version.
2393
2394=cut
2395
2396
# spent 38µs (32+6) within Foswiki::Meta::getLoadedRev which was called 6 times, avg 6µs/call: # 3 times (20µs+4µs) by Foswiki::META at line 16 of /var/www/foswiki11/lib/Foswiki/Macros/META.pm, avg 8µs/call # 3 times (12µs+2µs) by Foswiki::REVINFO at line 21 of /var/www/foswiki11/lib/Foswiki/Macros/REVINFO.pm, avg 5µs/call
sub getLoadedRev {
239762µs my $this = shift;
239867µs66µs _assertIsTopic($this) if DEBUG;
# spent 6µs making 6 calls to Assert::ASSERTS_OFF, avg 933ns/call
2399619µs return $this->{_loadedRev};
2400}
2401
2402=begin TML
2403
2404---++ ObjectMethod removeFromStore( $attachment )
2405 * =$attachment= - optional, provide to delete an attachment
2406
2407Use with great care! Removes all trace of the given web, topic
2408or attachment from the store, possibly including all its history.
2409
2410Also does not ensure consistency of the store
2411(for eg, if you delete an attachment, it does not update the intopic META)
2412
2413=cut
2414
2415sub removeFromStore {
2416 my ( $this, $attachment ) = @_;
2417 my $store = $this->{_session}->{store};
2418 ASSERT( $this->{_web}, 'this is not a removable object' ) if DEBUG;
2419
2420 if ( !$store->webExists( $this->{_web} ) ) {
2421 throw Error::Simple( 'No such web ' . $this->{_web} );
2422 }
2423 if ( $this->{_topic}
2424 && !$store->topicExists( $this->{_web}, $this->{_topic} ) )
2425 {
2426 throw Error::Simple(
2427 'No such topic ' . $this->{_web} . '.' . $this->{_topic} );
2428 }
2429
2430 if ( $attachment && !$this->hasAttachment($attachment) ) {
2431 ASSERT( $this->{topic}, 'this is not a removable object' ) if DEBUG;
2432 throw Error::Simple( 'No such attachment '
2433 . $this->{_web} . '.'
2434 . $this->{_topic} . '.'
2435 . $attachment );
2436 }
2437
2438 $store->remove( $this->{_session}->{user}, $this, $attachment );
2439}
2440
2441=begin TML
2442
2443---++ ObjectMethod getDifferences( $rev2, $contextLines ) -> \@diffArray
2444
2445Get the differences between the rev loaded into this object, and another
2446rev of the same topic. Return reference to an array of differences.
2447 * =$rev2= - the other revision to diff against
2448 * =$contextLines= - number of lines of context required
2449
2450Each difference is of the form [ $type, $right, $left ] where
2451| *type* | *Means* |
2452| =+= | Added |
2453| =-= | Deleted |
2454| =c= | Changed |
2455| =u= | Unchanged |
2456| =l= | Line Number |
2457
2458=cut
2459
2460sub getDifferences {
2461 my ( $this, $rev2, $contextLines ) = @_;
2462 _assertIsTopic($this) if DEBUG;
2463 return $this->{_session}->{store}
2464 ->getRevisionDiff( $this, $rev2, $contextLines );
2465}
2466
2467=begin TML
2468
2469---++ ObjectMethod getRevisionAtTime( $time ) -> $rev
2470 * =$time= - time (in epoch secs) for the rev
2471
2472Get the revision number for a topic at a specific time.
2473Returns a single-digit rev number or 0 if it couldn't be determined
2474(either because the topic isn't that old, or there was a problem)
2475
2476=cut
2477
2478sub getRevisionAtTime {
2479 my ( $this, $time ) = @_;
2480 _assertIsTopic($this) if DEBUG;
2481 return $this->{_session}->{store}->getRevisionAtTime( $this, $time );
2482}
2483
2484=begin TML
2485
2486---++ ObjectMethod setLease( $length )
2487
2488Take out an lease on the given topic for this user for $length seconds.
2489
2490See =getLease= for more details about Leases.
2491
2492=cut
2493
2494sub setLease {
2495 my ( $this, $length ) = @_;
2496 _assertIsTopic($this) if DEBUG;
2497 my $t = time();
2498 my $lease = {
2499 user => $this->{_session}->{user},
2500 expires => $t + $length,
2501 taken => $t
2502 };
2503 return $this->{_session}->{store}->setLease( $this, $lease );
2504}
2505
2506=begin TML
2507
2508---++ ObjectMethod getLease() -> $lease
2509
2510If there is an lease on the topic, return the lease, otherwise undef.
2511A lease is a block of meta-information about a topic that can be
2512recovered (this is a hash containing =user=, =taken= and =expires=).
2513Leases are taken out when a topic is edited. Only one lease
2514can be active on a topic at a time. Leases are used to warn if
2515another user is already editing a topic.
2516
2517=cut
2518
2519sub getLease {
2520 my $this = shift;
2521 _assertIsTopic($this) if DEBUG;
2522 return $this->{_session}->{store}->getLease($this);
2523}
2524
2525=begin TML
2526
2527---++ ObjectMethod clearLease()
2528
2529Cancel the current lease.
2530
2531See =getLease= for more details about Leases.
2532
2533=cut
2534
2535sub clearLease {
2536 my $this = shift;
2537 _assertIsTopic($this) if DEBUG;
2538 $this->{_session}->{store}->setLease($this);
2539}
2540
2541=begin TML
2542
2543---++ ObjectMethod onTick($time)
2544
2545Method invoked at regular intervals, usually by a cron job. The job of
2546this method is to prod the store into cleaning up expired leases, and
2547any other admin job that needs doing at regular intervals.
2548
2549=cut
2550
2551sub onTick {
2552 my ( $this, $time ) = @_;
2553
2554 if ( !$this->{_topic} ) {
2555 my $it = $this->eachWeb();
2556 while ( $it->hasNext() ) {
2557 my $web = $it->next();
2558 $web = $this->getPath() . "/$web" if $this->getPath();
2559 my $m = $this->new( $this->{_session}, $web );
2560 $m->onTick($time);
2561 }
2562 if ( $this->{_web} ) {
2563 $it = $this->eachTopic();
2564 while ( $it->hasNext() ) {
2565 my $topic = $it->next();
2566 my $topicObject =
2567 $this->new( $this->{_session}, $this->getPath(), $topic );
2568 $topicObject->onTick($time);
2569 }
2570 }
2571
2572 # Clean up spurious leases that may have been left behind
2573 # during cancelled topic creation
2574 $this->{_session}->{store}->removeSpuriousLeases( $this->getPath() )
2575 if $this->getPath();
2576 }
2577 else {
2578 my $lease = $this->getLease();
2579 if ( $lease && $lease->{expires} < $time ) {
2580 $this->clearLease();
2581 }
2582 }
2583}
2584
2585############# ATTACHMENTS ON TOPICS #############
2586
2587=begin TML
2588
2589---++ ObjectMethod getAttachmentRevisionInfo($attachment, $rev) -> \%info
2590 * =$attachment= - attachment name
2591 * =$rev= - optional integer attachment revision number
2592Get revision info for an attachment. Only valid on topics.
2593
2594$info will contain at least: date, author, version, comment
2595
2596=cut
2597
2598sub getAttachmentRevisionInfo {
2599 my ( $this, $attachment, $fromrev ) = @_;
2600 _assertIsTopic($this) if DEBUG;
2601
2602 return $this->{_session}->{store}
2603 ->getAttachmentVersionInfo( $this, $fromrev, $attachment );
2604}
2605
2606=begin TML
2607
2608---++ ObjectMethod attach ( %opts )
2609
2610 * =%opts= may include:
2611 * =name= - Name of the attachment
2612 * =dontlog= - don't add to statistics
2613 * =comment= - comment for save
2614 * =hide= - if the attachment is to be hidden in normal topic view
2615 * =stream= - Stream of file to upload. Uses =file= if not set.
2616 * =file= - Name of a *server* file to use for the attachment
2617 data. This should be passed if it is known, as it may be used
2618 to optimise handler calls.
2619 * =filepath= - Optional. Client path to file.
2620 * =filesize= - Optional. Size of uploaded data.
2621 * =filedate= - Optional. Date of file.
2622 * =author= - Optional. cUID of author of change. Defaults to current.
2623 * =notopicchange= - Optional. if the topic is *not* to be modified.
2624 This may result in incorrect meta-data stored in the topic, so must
2625 be used with care. Only has a meaning if the store implementation
2626 stores meta-data in topics.
2627
2628Saves a new revision of the attachment, invoking plugin handlers as
2629appropriate. This method automatically updates the loaded rev of $this
2630to the latest topic revision.
2631
2632If neither of =stream= or =file= are set, this is a properties-only save.
2633
2634Throws an exception on error.
2635
2636=cut
2637
2638# SMELL: arguably should only be permitted if the loaded rev of the object is the same as the
2639# latest rev.
2640
2641sub attach {
2642 my $this = shift;
2643 my %opts = @_;
2644 my $action;
2645 my $plugins = $this->{_session}->{plugins};
2646 _assertIsTopic($this) if DEBUG;
2647
2648 if ( $opts{file} && !$opts{stream} ) {
2649
2650 # no stream given, but a file was given; open it.
2651 open( $opts{stream}, '<', $opts{file} )
2652 || throw Error::Simple( 'Could not open ' . $opts{file} );
2653 binmode( $opts{stream} )
2654 || throw Error::Simple( $opts{file} . ' binmode failed: ' . $! );
2655 }
2656
2657 my $attrs;
2658 if ( $opts{stream} ) {
2659 $action = 'upload';
2660
2661 $attrs = {
2662 name => $opts{name},
2663 attachment => $opts{name},
2664 stream => $opts{stream},
2665 user => $this->{_session}->{user}, # cUID
2666 comment => defined $opts{comment} ? $opts{comment} : '',
2667 };
2668
2669 if ( $plugins->haveHandlerFor('beforeAttachmentSaveHandler') ) {
2670
2671 # *Deprecated* handler.
2672
2673 # The handler may have been called as a result of an upload,
2674 # in which case the data is already in a file in the CGI cache,
2675 # and the stream is valid, or it may be been arrived at via a
2676 # call to Func::saveAttachment, in which case it's possible that
2677 # the stream isn't open but we have a tmpFilename instead.
2678 #
2679 $attrs->{tmpFilename} = $opts{file};
2680
2681 if ( !defined( $attrs->{tmpFilename} ) ) {
2682
2683 # CGI (or the caller) did not provide a temporary file
2684
2685 # Stream the data to a temporary file, so it can be passed
2686 # to the handler.
2687
2688 require File::Temp;
2689
2690 my $fh = new File::Temp();
2691 binmode($fh);
2692
2693 # transfer 512KB blocks
2694 my $transfer;
2695 my $r;
2696 while ( $r = sysread( $opts{stream}, $transfer, 0x80000 ) ) {
2697 if ( !defined $r ) {
2698 next if ( $! == Errno::EINTR );
2699 die "system read error: $!\n";
2700 }
2701 my $offset = 0;
2702 while ($r) {
2703 my $w = syswrite( $fh, $transfer, $r, $offset );
2704 die "system write error: $!\n" unless ( defined $w );
2705 $offset += $w;
2706 $r -= $w;
2707 }
2708 }
2709 select( ( select($fh), $| = 1 )[0] );
2710
2711 # $fh->seek only in File::Temp 0.17 and later
2712 seek( $fh, 0, 0 ) or die "Can't seek temp: $!\n";
2713 $opts{stream} = $fh;
2714 $attrs->{tmpFilename} = $fh->filename();
2715 }
2716
2717 if ( $plugins->haveHandlerFor('beforeAttachmentSaveHandler') ) {
2718 $plugins->dispatch( 'beforeAttachmentSaveHandler', $attrs,
2719 $this->{_topic}, $this->{_web} );
2720 }
2721
2722 # Have to assume it's changed, even if it hasn't.
2723 open( $attrs->{stream}, '<', $attrs->{tmpFilename} )
2724 || die "Internal error: $!";
2725 binmode( $attrs->{stream} );
2726 $opts{stream} = $attrs->{stream};
2727
2728 delete $attrs->{tmpFilename};
2729 }
2730
2731 if ( $plugins->haveHandlerFor('beforeUploadHandler') ) {
2732
2733 # Check the stream is seekable
2734 ASSERT(
2735 seek( $attrs->{stream}, 0, 1 ),
2736 'Stream for attachment is not seekable'
2737 ) if DEBUG;
2738
2739 $plugins->dispatch( 'beforeUploadHandler', $attrs, $this );
2740 $opts{stream} = $attrs->{stream};
2741 seek( $opts{stream}, 0, 0 ); # seek to beginning
2742 binmode( $opts{stream} );
2743 }
2744
2745 # Force reload of the latest version
2746 $this = $this->load() unless $this->latestIsLoaded();
2747
2748 my $error;
2749 try {
2750 $this->{_session}->{store}
2751 ->saveAttachment( $this, $opts{name}, $opts{stream},
2752 $opts{author} || $this->{_session}->{user},
2753 $opts{comment} );
2754 }
2755 finally {
2756 $this->fireDependency();
2757 };
2758
2759 my $fileVersion = $this->getLatestRev( $opts{name} );
2760 $attrs->{version} = $fileVersion;
2761 $attrs->{path} = $opts{filepath} if ( defined( $opts{filepath} ) );
2762 $attrs->{size} = $opts{filesize} if ( defined( $opts{filesize} ) );
2763 $attrs->{date} = defined $opts{filedate} ? $opts{filedate} : time();
2764
2765 if ( $plugins->haveHandlerFor('afterAttachmentSaveHandler') ) {
2766
2767 # *Deprecated* handler
2768 $plugins->dispatch( 'afterAttachmentSaveHandler', $attrs,
2769 $this->{_topic}, $this->{_web} );
2770 }
2771 }
2772 else {
2773
2774 # Property change
2775 $action = 'save';
2776 $attrs = $this->get( 'FILEATTACHMENT', $opts{name} );
2777 $attrs->{name} = $opts{name};
2778 $attrs->{comment} = $opts{comment} if ( defined( $opts{comment} ) );
2779 }
2780 $attrs->{attr} = ( $opts{hide} ) ? 'h' : '';
2781 delete $attrs->{stream};
2782 delete $attrs->{tmpFilename};
2783 $this->putKeyed( 'FILEATTACHMENT', $attrs );
2784
2785 if ( $opts{createlink} ) {
2786 my $text = $this->text();
2787 $text = '' unless defined $text;
2788 $text .=
2789 $this->{_session}->attach->getAttachmentLink( $this, $opts{name} );
2790 $this->text($text);
2791 }
2792
2793 $this->saveAs() unless $opts{notopicchange};
2794
2795 my @extras = ( $opts{name} );
2796 push( @extras, 'dontlog' ) if $opts{dontlog}; # no statistics
2797 $this->{_session}->logEvent(
2798 $action,
2799 $this->{_web} . '.' . $this->{_topic},
2800 join( ', ', @extras ),
2801 $this->{_session}->{user}
2802 );
2803
2804 if ( $plugins->haveHandlerFor('afterUploadHandler') ) {
2805 $plugins->dispatch( 'afterUploadHandler', $attrs, $this );
2806 }
2807}
2808
2809=begin TML
2810
2811---++ ObjectMethod hasAttachment( $name ) -> $boolean
2812Test if the named attachment exists. Only valid on topics. The attachment
2813must exist in the store (it is not sufficient for it to be referenced
2814in the object only)
2815
2816=cut
2817
2818
# spent 178µs (40+138) within Foswiki::Meta::hasAttachment which was called: # once (40µs+138µs) by Foswiki::_lookupIcon at line 48 of /var/www/foswiki11/lib/Foswiki/Macros/ICON.pm
sub hasAttachment {
281912µs my ( $this, $name ) = @_;
282012µs11µs _assertIsTopic($this) if DEBUG;
# spent 1µs making 1 call to Assert::ASSERTS_OFF
2821124µs1136µs return $this->{_session}->{store}->attachmentExists( $this, $name );
# spent 136µs making 1 call to Foswiki::Store::VC::Store::attachmentExists
2822}
2823
2824=begin TML
2825
2826---++ ObjectMethod testAttachment( $name, $test ) -> $value
2827
2828Performs a type test on the given attachment file.
2829 * =$name= - name of the attachment to test e.g =lolcat.gif=
2830 * =$test= - the test to perform e.g. ='r'=
2831
2832The return value is the value that would be returned by the standard
2833perl file operations, as indicated by $type
2834
2835 * r File is readable by current user (tests Foswiki VIEW permission)
2836 * w File is writable by current user (tests Foswiki CHANGE permission)
2837 * e File exists.
2838 * z File has zero size.
2839 * s File has nonzero size (returns size).
2840 * T File is an ASCII text file (heuristic guess).
2841 * B File is a "binary" file (opposite of T).
2842 * M Last modification time (epoch seconds).
2843 * A Last access time (epoch seconds).
2844
2845Note that all these types should behave as the equivalent standard perl
2846operator behaves, except M and A which are independent of the script start
2847time (see perldoc -f -X for more information)
2848
2849Other standard Perl file tests may also be supported on some store
2850implementations, but cannot be relied on.
2851
2852Errors will be signalled by an Error::Simple exception.
2853
2854=cut
2855
2856sub testAttachment {
2857 my ( $this, $attachment, $test ) = @_;
2858 _assertIsTopic($this) if DEBUG;
2859
2860 $this->addDependency();
2861
2862 $test =~ /(\w)/;
2863 $test = $1;
2864 if ( $test eq 'r' ) {
2865 return $this->haveAccess('VIEW');
2866 }
2867 elsif ( $test eq 'w' ) {
2868 return $this->haveAccess('CHANGE');
2869 }
2870
2871 return
2872 return $this->{_session}->{store}
2873 ->testAttachment( $this, $attachment, $test );
2874}
2875
2876=begin TML
2877
2878---+++ openAttachment($attachment, $mode, %opts) -> $fh
2879 * =$attachment= - the attachment
2880 * =$mode= - mode to open the attachment in
2881Opens a stream onto the attachment. This method is primarily to
2882support virtual file systems, and as such access controls are *not*
2883checked, plugin handlers are *not* called, and it does *not* update the
2884meta-data in the topicObject.
2885
2886=$mode= can be '&lt;', '&gt;' or '&gt;&gt;' for read, write, and append
2887respectively.
2888
2889=%opts= can take different settings depending on =$mode=.
2890 * =$mode='&lt;'=
2891 * =version= - revision of the object to open e.g. =version => 6=
2892 * =$mode='&gt;'= or ='&gt;&gt;'
2893 * no options
2894Errors will be signalled by an =Error= exception.
2895
2896See also =attach= if this function is too basic for you.
2897
2898=cut
2899
2900sub openAttachment {
2901 my ( $this, $attachment, $mode, @opts ) = @_;
2902 _assertIsTopic($this) if DEBUG;
2903
2904 return $this->{_session}->{store}
2905 ->openAttachment( $this, $attachment, $mode, @opts );
2906
2907}
2908
2909=begin TML
2910
2911---++ ObjectMethod moveAttachment( $name, $to, %opts ) -> $data
2912Move the named attachment to the topic indicates by $to.
2913=%opts= may include:
2914 * =new_name= - new name for the attachment
2915 * =user= - cUID of user doing the moving
2916
2917=cut
2918
2919sub moveAttachment {
2920 my $this = shift;
2921 my $name = shift;
2922 my $to = shift;
2923 my %opts = @_;
2924 my $cUID = $opts{user} || $this->{_session}->{user};
2925 _assertIsTopic($this) if DEBUG;
2926 $to->_assertIsTopic() if DEBUG;
2927
2928 my $newName = $opts{new_name} || $name;
2929
2930 # Make sure we have latest revs
2931 $this = $this->load() unless $this->latestIsLoaded();
2932
2933 $this->_atomicLock($cUID);
2934 $to->_atomicLock($cUID);
2935
2936 try {
2937 $this->{_session}->{store}
2938 ->moveAttachment( $this, $name, $to, $newName, $cUID );
2939
2940 # Modify the cache of the old topic
2941 my $fileAttachment = $this->get( 'FILEATTACHMENT', $name );
2942 $this->remove( 'FILEATTACHMENT', $name );
2943 $this->saveAs(
2944 undef, undef,
2945 dontlog => 1, # no statistics
2946 comment => 'lost ' . $name
2947 );
2948
2949 # Add file attachment to new topic
2950 $fileAttachment->{name} = $newName;
2951 $fileAttachment->{movefrom} = $this->getPath() . '.' . $name;
2952 $fileAttachment->{moveby} =
2953 $this->{_session}->{users}->getLoginName($cUID);
2954 $fileAttachment->{movedto} = $to->getPath() . '.' . $newName;
2955 $fileAttachment->{movedwhen} = time();
2956 $to->loadVersion();
2957 $to->putKeyed( 'FILEATTACHMENT', $fileAttachment );
2958
2959 if ( $this->getPath() eq $to->getPath() ) {
2960 $to->remove( 'FILEATTACHMENT', $name );
2961 }
2962
2963 $to->saveAs(
2964 undef, undef,
2965 dontlog => 1, # no statistics
2966 comment => 'gained' . $newName
2967 );
2968 }
2969 finally {
2970 $to->_atomicUnlock($cUID);
2971 $this->_atomicUnlock($cUID);
2972 $this->fireDependency();
2973 $to->fireDependency();
2974 };
2975
2976 # alert plugins of attachment move
2977 $this->{_session}->{plugins}
2978 ->dispatch( 'afterRenameHandler', $this->{_web}, $this->{_topic}, $name,
2979 $to->{_web}, $to->{_topic}, $newName );
2980
2981 $this->{_session}->logEvent(
2982 'move',
2983 $this->getPath() . '.'
2984 . $name
2985 . ' moved to '
2986 . $to->getPath() . '.'
2987 . $newName,
2988 $cUID
2989 );
2990}
2991
2992=begin TML
2993
2994---++ ObjectMethod copyAttachment( $name, $to, %opts ) -> $data
2995Copy the named attachment to the topic indicates by $to.
2996=%opts= may include:
2997 * =new_name= - new name for the attachment
2998 * =user= - cUID of user doing the moving
2999
3000=cut
3001
3002sub copyAttachment {
3003 my $this = shift;
3004 my $name = shift;
3005 my $to = shift;
3006 my %opts = @_;
3007 my $cUID = $opts{user} || $this->{_session}->{user};
3008 _assertIsTopic($this) if DEBUG;
3009 $to->_assertIsTopic() if DEBUG;
3010
3011 my $newName = $opts{new_name} || $name;
3012
3013 # Make sure we have latest revs
3014 my $from;
3015 if ( $this->latestIsLoaded() ) {
3016 $from = $this;
3017 }
3018 else {
3019 $from = $this->load();
3020 }
3021
3022 $from->_atomicLock($cUID);
3023 $to->_atomicLock($cUID);
3024
3025 try {
3026 $from->{_session}->{store}
3027 ->copyAttachment( $from, $name, $to, $newName, $cUID );
3028
3029 # Add file attachment to new topic by copying the old one
3030 my $fileAttachment = { %{ $from->get( 'FILEATTACHMENT', $name ) } };
3031 $fileAttachment->{name} = $newName;
3032
3033 $to->loadVersion() unless $to->latestIsLoaded();
3034 $to->putKeyed( 'FILEATTACHMENT', $fileAttachment );
3035
3036 if ( $from->getPath() eq $to->getPath() ) {
3037 $to->remove( 'FILEATTACHMENT', $name );
3038 }
3039
3040 $to->saveAs(
3041 undef, undef,
3042 author => $cUID,
3043 dontlog => 1, # no statistics
3044 comment => 'gained' . $newName
3045 );
3046 }
3047 finally {
3048 $to->_atomicUnlock($cUID);
3049 $from->_atomicUnlock($cUID);
3050 $from->fireDependency();
3051 $to->fireDependency();
3052 };
3053
3054 # alert plugins of attachment move
3055 # SMELL: no defined handler for attachment copies
3056 # $this->{_session}->{plugins}
3057 # ->dispatch( 'afterCopyHandler', $this->{_web}, $this->{_topic}, $name,
3058 # $to->{_web}, $to->{_topic}, $newName );
3059
3060 $this->{_session}->logEvent(
3061 'copy',
3062 $this->getPath() . '.'
3063 . $name
3064 . ' copied to '
3065 . $to->getPath() . '.'
3066 . $newName,
3067 $cUID
3068 );
3069}
3070
3071=begin TML
3072
3073---++ ObjectMethod expandNewTopic()
3074Expand only that subset of Foswiki variables that are
3075expanded during topic creation, in the body text and
3076PREFERENCE meta only.
3077
3078The expansion is in-place in the object data.
3079
3080Only valid on topics.
3081
3082=cut
3083
3084sub expandNewTopic {
3085 my ($this) = @_;
3086 _assertIsTopic($this) if DEBUG;
3087 $this->{_session}->expandMacrosOnTopicCreation($this);
3088}
3089
3090=begin TML
3091
3092---++ ObjectMethod expandMacros( $text ) -> $text
3093Expand only all Foswiki variables that are
3094expanded during topic view. Returns the expanded text.
3095Only valid on topics.
3096
3097=cut
3098
3099
# spent 59.1s (86µs+59.1) within Foswiki::Meta::expandMacros which was called 7 times, avg 8.45s/call: # 3 times (52µs+59.1s) by Foswiki::UI::View::_prepare at line 412 of /var/www/foswiki11/lib/Foswiki/UI/View.pm, avg 19.7s/call # 2 times (21µs+16.3ms) by Foswiki::_renderZone at line 3589 of /var/www/foswiki11/lib/Foswiki.pm, avg 8.18ms/call # 2 times (13µs+1.05ms) by Foswiki::Func::expandCommonVariables at line 2533 of /var/www/foswiki11/lib/Foswiki/Func.pm, avg 530µs/call
sub expandMacros {
3100716µs my ( $this, $text ) = @_;
3101710µs78µs _assertIsTopic($this) if DEBUG;
# spent 8µs making 7 calls to Assert::ASSERTS_OFF, avg 1µs/call
3102
3103752µs759.1s return $this->{_session}->expandMacros( $text, $this );
# spent 59.1s making 7 calls to Foswiki::expandMacros, avg 8.45s/call
3104}
3105
3106=begin TML
3107
3108---++ ObjectMethod renderTML( $text ) -> $text
3109Render all TML constructs in the text into HTML. Returns the rendered text.
3110Only valid on topics.
3111
3112=cut
3113
3114
# spent 61.5ms (82µs+61.5) within Foswiki::Meta::renderTML which was called 5 times, avg 12.3ms/call: # 3 times (60µs+58.0ms) by Foswiki::UI::View::_prepare at line 413 of /var/www/foswiki11/lib/Foswiki/UI/View.pm, avg 19.4ms/call # 2 times (21µs+3.44ms) by Foswiki::_renderZone at line 3590 of /var/www/foswiki11/lib/Foswiki.pm, avg 1.73ms/call
sub renderTML {
3115514µs my ( $this, $text ) = @_;
311658µs55µs _assertIsTopic($this) if DEBUG;
# spent 5µs making 5 calls to Assert::ASSERTS_OFF, avg 1µs/call
3117548µs1061.5ms return $this->{_session}->renderer->getRenderedVersion( $text, $this );
# spent 61.4ms making 5 calls to Foswiki::Render::getRenderedVersion, avg 12.3ms/call # spent 16µs making 5 calls to Foswiki::renderer, avg 3µs/call
3118}
3119
3120=begin TML
3121
3122---++ ObjectMethod summariseText( $flags [, $text, \%searchOptions] ) -> $tml
3123
3124Makes a plain text summary of the topic text by simply trimming a bit
3125off the top. Truncates to $TMTRUNC chars or, if a number is specified
3126in $flags, to that length.
3127
3128If $text is defined, use it in place of the topic text.
3129
3130The =\%searchOptions= hash may contain the following options:
3131 * =type= - search type: keyword, literal, query
3132 * =casesensitive= - false to ignore case (default true)
3133 * =wordboundaries= - if type is 'keyword'
3134 * =tokens= - array ref of search tokens
3135
3136TODO: should this really be in Meta? it seems like a rendering issue to me.
3137
3138warning: this will produce text that contains html entities - including quotes
3139use =$summary = Foswiki::entityEncode($summary);= to diffuse them
3140
3141
3142=cut
3143
3144sub summariseText {
3145 my ( $this, $flags, $text, $searchOptions ) = @_;
3146 _assertIsTopic($this) if DEBUG;
3147
3148 $flags ||= '';
3149
3150 $text = $this->text() unless defined $text;
3151 $text = '' unless defined $text;
3152
3153 my $plainText =
3154 $this->session->renderer->TML2PlainText( $text, $this, $flags );
3155 $plainText =~ s/\n+/ /g;
3156
3157 # limit to n chars
3158 my $limit = $flags || '';
3159 unless ( $limit =~ s/^.*?([0-9]+).*$/$1/ ) {
3160 $limit = $SUMMARY_TMLTRUNC;
3161 }
3162 $limit = $SUMMARY_MINTRUNC if ( $limit < $SUMMARY_MINTRUNC );
3163
3164 if ( $flags =~ m/searchcontext/ ) {
3165 return $this->_summariseTextWithSearchContext( $plainText, $limit,
3166 $searchOptions );
3167 }
3168 else {
3169 return $this->_summariseTextSimple( $plainText, $limit );
3170 }
3171}
3172
3173=begin TML
3174
3175---++ ObjectMethod _summariseTextSimple( $text, $limit ) -> $tml
3176
3177Makes a plain text summary of the topic text by simply trimming a bit
3178off the top. Truncates to $TMTRUNC chars or, if a number is specified
3179in $flags, to that length.
3180
3181TODO: should this really be in Meta? it seems like a rendering issue to me.
3182
3183=cut
3184
3185sub _summariseTextSimple {
3186 my ( $this, $text, $limit ) = @_;
3187 _assertIsTopic($this) if DEBUG;
3188
3189 # SMELL: need to avoid splitting within multi-byte characters
3190 # by encoding bytes as Perl UTF-8 characters.
3191 # This avoids splitting within a Unicode codepoint (or a UTF-16
3192 # surrogate pair, which is encoded as a single Perl UTF-8 character),
3193 # but we ideally need to avoid splitting closely related Unicode
3194 # codepoints.
3195 # Specifically, this means Unicode combining character sequences (e.g.
3196 # letters and accents)
3197 # Might be better to split on \b if possible.
3198
3199 $text =~
3200 s/^(.{$limit}.*?)($Foswiki::regex{mixedAlphaNumRegex}).*$/$1$2 \.\.\./s;
3201
3202 return $this->_makeSummaryTextSafe($text);
3203}
3204
3205sub _makeSummaryTextSafe {
3206 my ( $this, $text ) = @_;
3207
3208 my $session = $this->session();
3209 my $renderer = $session->renderer();
3210
3211 # We do not want the summary to contain any $variable that formatted
3212 # searches can interpret to anything (Item3489).
3213 # Especially new lines (Item2496)
3214 # To not waste performance we simply replace $ by $<nop>
3215 $text =~ s/\$/\$<nop>/g;
3216
3217 # Escape Interwiki links and other side effects introduced by
3218 # plugins later in the rendering pipeline (Item4748)
3219 $text =~ s/\:/<nop>\:/g;
3220 $text =~ s/\s+/ /g;
3221
3222 return $this->session->renderer->protectPlainText($text);
3223}
3224
3225=begin TML
3226
3227---++ ObjectMethod _summariseTextWithSearchContext( $text, $limit, $type, $searchOptions ) -> $tml
3228
3229Improves 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.
3230
3231The =\%searchOptions= hash may contain the following options:
3232 * =type= - search type: keyword, literal, query
3233 * =casesensitive= - false to ignore case (default true)
3234 * =wordboundaries= - if type is 'keyword'
3235 * =tokens= - array ref of search tokens
3236
3237=cut
3238
3239sub _summariseTextWithSearchContext {
3240 my ( $this, $text, $limit, $searchOptions ) = @_;
3241
3242 if ( !$searchOptions->{tokens} ) {
3243 return $this->_summariseTextSimple( $text, $limit );
3244 }
3245
3246 my $type = $searchOptions->{type} || '';
3247 if ( $type ne 'keyword' && $type ne 'literal' && $type ne '' ) {
3248 return $this->_summariseTextSimple( $text, $limit );
3249 }
3250
3251 my $caseSensitive = $searchOptions->{casesensitive} || '';
3252 my $wordBoundaries = $searchOptions->{wordboundaries} || '';
3253
3254#Item12166
3255#NOTE: this is duplicating the F::Search::Node code, and probably the F::Q:: =~ parse
3256#and the SearchAlgo already deals with this issue to some degree (i'm not sure it does unmatched [ etc)
3257
3258 my $tToken;
3259 my @tokens = map {
3260
3261 $tToken = $_; # copy $_ to avoid changing the passed token
3262
3263#we get a crash if the tokem is not a valid regex. - for eg a single lone *
3264#actually need to escape all things that would trash the regex
3265#TODO: this needs to be extracted from here and Forking.pm and pushed into F::Search::Node
3266 $tToken =~ s#([][|/\\\$\^*()+{};@?.{}])#\\$1#g if ( $type ne 'regex' );
3267 $tToken;
3268 } grep { !/^!.*$/ } @{ $searchOptions->{tokens} };
3269 my $keystrs = join( '|', @tokens );
3270
3271 if ( !$keystrs ) {
3272 return $this->_summariseTextSimple( $text, $limit );
3273 }
3274
3275 # we don't have a means currently to set the word window through a parameter
3276 # so we always use the default
3277 my $context = $SUMMARY_DEFAULT_CONTEXT;
3278
3279# break on words with search type 'word' (which is passed as type 'keyword' with $wordBoundaries as true
3280 my $wordBoundaryAnchor =
3281 ( $type eq 'keyword' && $wordBoundaries ) ? '\b' : '';
3282 $keystrs = $caseSensitive ? "($keystrs)" : "((?i:$keystrs))";
3283 my $termsPattern = $wordBoundaryAnchor . $keystrs . $wordBoundaryAnchor;
3284
3285# 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
3286 my $beforePattern = "(\\b.{0,$context}$wordBoundaryAnchor)";
3287 my $afterPattern = "($wordBoundaryAnchor.{0,$context}\\b)";
3288 my $searchPattern = $beforePattern . $termsPattern . $afterPattern;
3289
3290 my $summary = '';
3291 my $summaryLength = 0;
3292 while ( $summaryLength < $limit && $text =~ m/$searchPattern/gs ) {
3293 my $before = $1 || '';
3294 my $term = $2 || '';
3295 my $after = $3 || '';
3296
3297 $before = $this->_makeSummaryTextSafe($before);
3298 $term = $this->_makeSummaryTextSafe($term);
3299 $after = $this->_makeSummaryTextSafe($after);
3300
3301 $summaryLength += length "$before$term$after";
3302
3303 my $startLoc = $-[0];
3304
3305 # only show ellipsis when not at the start
3306 # and when we don't have any summary text yet
3307 if ( !$summary && $startLoc != 0 ) {
3308 $before = "$SUMMARY_ELLIPSIS $before";
3309 }
3310
3311 my $endLoc = $+[0] || $-[0];
3312 $after = "$after $SUMMARY_ELLIPSIS" if $endLoc != length $text;
3313
3314 $summary .= $before . CGI::em( {}, $term ) . $after . ' ';
3315 }
3316
3317 return $this->_summariseTextSimple( $text, $limit ) if !$summary;
3318
3319 return $summary;
3320}
3321
3322=begin TML
3323
3324---++ ObjectMethod summariseChanges( $orev, $nrev, $tml) -> $text
3325
3326Generate a (max 3 line) summary of the differences between the revs.
3327
3328 * =$orev= - older rev, if not defined will use ($nrev - 1)
3329 * =$nrev= - later rev, if not defined defaults to latest
3330 * =$tml= - if true will generate renderable TML (i.e. HTML with NOPs.
3331 If false will generate a summary suitable for use in plain text
3332 (mail, for example)
3333
3334If there is only one rev, a topic summary will be returned.
3335
3336If =$tml= is not set, all HTML will be removed.
3337
3338In non-tml, lines are truncated to 70 characters. Differences are shown using + and - to indicate added and removed text.
3339
3340=cut
3341
3342sub summariseChanges {
3343 my ( $this, $orev, $nrev, $tml ) = @_;
3344 my $summary = '';
3345 my $session = $this->session();
3346 my $renderer = $session->renderer();
3347
3348 _assertIsTopic($this) if DEBUG;
3349 $nrev = $this->getLatestRev() unless $nrev;
3350
3351 ASSERT( $nrev =~ /^\s*\d+\s*/ ) if DEBUG; # looks like a number
3352
3353 $orev = $nrev - 1 unless defined($orev);
3354
3355 ASSERT( $orev =~ /^\s*\d+\s*/ ) if DEBUG; # looks like a number
3356 ASSERT( $orev >= 0 ) if DEBUG;
3357 ASSERT( $nrev >= $orev ) if DEBUG;
3358
3359 unless ( defined $this->{_loadedRev} && $this->{_loadedRev} eq $nrev ) {
3360 $this = $this->load($nrev);
3361 }
3362
3363 my $ntext = '';
3364 if ( $this->haveAccess('VIEW') ) {
3365
3366 # Only get the text if we have access to nrev
3367 $ntext = $this->text();
3368 }
3369
3370 return '' if ( $orev == $nrev ); # same rev, no differences
3371
3372 my $nstring = $this->stringify();
3373 $nstring =~ s/^%META:TOPICINFO{.*?}%//ms;
3374
3375 #print "SSSSSS nstring\n($nstring)\nSSSSSS\n\n";
3376
3377 $ntext = $renderer->TML2PlainText( $nstring, $this, 'showvar showmeta' );
3378
3379 #print "SSSSSS ntext\n($ntext)\nSSSSSS\n\n";
3380
3381 my $oldTopicObject =
3382 Foswiki::Meta->load( $session, $this->web, $this->topic, $orev );
3383 unless ( $oldTopicObject->haveAccess('VIEW') ) {
3384
3385 # No access to old rev, make a blank topic object
3386 $oldTopicObject =
3387 Foswiki::Meta->new( $session, $this->web, $this->topic, '' );
3388 }
3389
3390 my $ostring = $oldTopicObject->stringify();
3391 $ostring =~ s/^%META:TOPICINFO{.*?}%$//ms;
3392
3393 #print "SSSSSS ostring\n$ostring\nSSSSSS\n\n";
3394
3395 my $otext =
3396 $renderer->TML2PlainText( $ostring, $oldTopicObject, 'showvar showmeta' );
3397
3398 #print "SSSSSS otext\n($otext)\nSSSSSS\n\n";
3399
3400 require Foswiki::Merge;
3401 my $blocks = Foswiki::Merge::simpleMerge( $otext, $ntext, qr/[\r\n]+/ );
3402
3403 #foreach $b ( @$blocks ) {
3404 # print "BBBB\n($b)\nBBBB\n\n";
3405 # }
3406
3407 # sort through, keeping one line of context either side of a change
3408 my @revised;
3409 my $getnext = 0;
3410 my $prev = '';
3411 my $ellipsis = $tml ? $SUMMARY_ELLIPSIS : '...';
3412 my $trunc = $tml ? $SUMMARY_TMLTRUNC : $CHANGES_SUMMARY_PLAINTRUNC;
3413 while ( scalar @$blocks && scalar(@revised) < $CHANGES_SUMMARY_LINECOUNT ) {
3414 my $block = shift(@$blocks);
3415 next unless $block =~ /\S/;
3416 my $trim = length($block) > $trunc;
3417 $block =~ s/^(.{$trunc}).*$/$1/ if ($trim);
3418 if ( $block =~ m/^[-+]/ ) {
3419 if ($tml) {
3420 $block =~ s/^-(.*)$/CGI::del( {}, $1 )/se;
3421 $block =~ s/^\+(.*)$/CGI::ins( {}, $1 )/se;
3422 }
3423 elsif ( $session->inContext('rss') ) {
3424 $block =~ s/^-/REMOVED: /;
3425 $block =~ s/^\+/INSERTED: /;
3426 }
3427 push( @revised, $prev ) if $prev;
3428 $block .= $ellipsis if $trim;
3429 push( @revised, $block );
3430 $getnext = 1;
3431 $prev = '';
3432 }
3433 else {
3434 if ($getnext) {
3435 $block .= $ellipsis if $trim;
3436 push( @revised, $block );
3437 $getnext = 0;
3438 $prev = '';
3439 }
3440 else {
3441 $prev = $block;
3442 }
3443 }
3444 }
3445 if ($tml) {
3446 $summary = join( CGI::br(), @revised );
3447 }
3448 else {
3449 $summary = join( "\n", @revised );
3450 }
3451
3452 unless ($summary) {
3453 return $this->summariseText( '', $ntext );
3454 }
3455
3456 #print "SUMMARY\n===================\n($summary)\n============\n\n";
3457
3458 if ( !$tml ) {
3459 $summary = $renderer->protectPlainText($summary);
3460 }
3461 return $summary;
3462}
3463
3464=begin TML
3465
3466---++ ObjectMethod getEmbeddedStoreForm() -> $text
3467
3468Generate the embedded store form of the topic. The embedded store
3469form has meta-data values embedded using %META: lines. The text
3470stored in the meta is taken as the topic text.
3471
3472TODO: Soooo.... if we wanted to make a meta->setPreference('VARIABLE', 'Values...'); we would have to change this to
3473 1 see if that preference is set in the {_text} using the * Set syntax, in which case, replace that
3474 2 or let the META::PREF.. work as it does now..
3475
3476yay :/
3477
3478=cut
3479
3480
# spent 3.87ms (90µs+3.78) within Foswiki::Meta::getEmbeddedStoreForm which was called 2 times, avg 1.94ms/call: # 2 times (90µs+3.78ms) by Foswiki::Func::readTopicText at line 3496 of /var/www/foswiki11/lib/Foswiki/Func.pm, avg 1.94ms/call
sub getEmbeddedStoreForm {
34812800ns my $this = shift;
348222µs22µs _assertIsTopic($this) if DEBUG;
# spent 2µs making 2 calls to Assert::ASSERTS_OFF, avg 750ns/call
348321µs $this->{_text} ||= '';
3484
348521µs require Foswiki::Store; # for encoding
3486
348723µs212µs my $ti = $this->get('TOPICINFO');
# spent 12µs making 2 calls to Foswiki::Meta::get, avg 6µs/call
348824µs delete $ti->{rev} if $ti; # don't want this written
3489
349025µs2184µs my $text = $this->_writeTypes( 'TOPICINFO', 'TOPICPARENT' );
# spent 184µs making 2 calls to Foswiki::Meta::_writeTypes, avg 92µs/call
349125µs $text .= $this->{_text};
3492219µs43.59ms my $end =
# spent 3.59ms making 4 calls to Foswiki::Meta::_writeTypes, avg 897µs/call
3493 $this->_writeTypes( 'FORM', 'FIELD', 'FILEATTACHMENT', 'TOPICMOVED' )
3494 . $this->_writeTypes(
3495 'not', 'TOPICINFO', 'TOPICPARENT', 'FORM',
3496 'FIELD', 'FILEATTACHMENT', 'TOPICMOVED'
3497 );
34982800ns $text .= "\n" if $end;
3499
350023µs $ti->{rev} = $ti->{version} if $ti;
3501
3502229µs return $text . $end;
3503}
3504
3505# PRIVATE STATIC Write a meta-data key=value pair
3506# The encoding is reversed in _readKeyValues
3507
# spent 2.18ms (1.63+550µs) within Foswiki::Meta::_writeKeyValue which was called 392 times, avg 6µs/call: # 352 times (1.47ms+495µs) by Foswiki::Meta::_writeTypes at line 3556, avg 6µs/call # 40 times (154µs+55µs) by Foswiki::Meta::_writeTypes at line 3550, avg 5µs/call
sub _writeKeyValue {
3508392188µs my ( $key, $value ) = @_;
3509
3510392386µs392550µs if ( defined($value) ) {
# spent 550µs making 392 calls to Foswiki::Meta::dataEncode, avg 1µs/call
3511 $value = dataEncode($value);
3512 }
3513 else {
3514 $value = '';
3515 }
3516
3517392921µs return $key . '="' . $value . '"';
3518}
3519
3520# PRIVATE STATIC: Write all the key=value pairs for the types listed
3521
# spent 3.77ms (1.59+2.18) within Foswiki::Meta::_writeTypes which was called 6 times, avg 628µs/call: # 4 times (1.50ms+2.08ms) by Foswiki::Meta::getEmbeddedStoreForm at line 3492, avg 897µs/call # 2 times (90µs+94µs) by Foswiki::Meta::getEmbeddedStoreForm at line 3490, avg 92µs/call
sub _writeTypes {
3522611µs my ( $this, @types ) = @_;
3523
352462µs my $text = '';
3525
352664µs if ( $types[0] eq 'not' ) {
3527
3528 # write all types that are not in the list
35292400ns my %seen;
353026µs @seen{@types} = ();
353122µs @types = (); # empty "not in list"
353227µs foreach my $key ( keys %$this ) {
35332020µs push( @types, $key )
3534 unless ( exists $seen{$key} || $key =~ /^_/ );
3535 }
3536 }
3537
353868µs foreach my $type (@types) {
3539143µs next if $type eq '_session';
3540146µs my $data = $this->{$type};
3541144µs next if !defined $data;
354266µs foreach my $item (@$data) {
3543448µs my $sep = '';
35444416µs $text .= '%META:' . $type . '{';
35454419µs my $name = $item->{name};
35464412µs if ($name) {
3547
3548 # If there's a name field, put first to make regexp
3549 # based searching easier
35504048µs40209µs $text .= _writeKeyValue( 'name', $item->{name} );
# spent 209µs making 40 calls to Foswiki::Meta::_writeKeyValue, avg 5µs/call
35514011µs $sep = ' ';
3552 }
355344182µs foreach my $key ( sort keys %$item ) {
3554392217µs if ( $key ne 'name' ) {
355535257µs $text .= $sep;
3556352463µs3521.97ms $text .= _writeKeyValue( $key, $item->{$key} );
# spent 1.97ms making 352 calls to Foswiki::Meta::_writeKeyValue, avg 6µs/call
355735284µs $sep = ' ';
3558 }
3559 }
35604421µs $text .= '}%' . "\n";
3561 }
3562 }
3563
3564627µs return $text;
3565}
3566
3567=begin TML
3568
3569---++ ObjectMethod setEmbeddedStoreForm( $text )
3570
3571Populate this object with embedded meta-data from $text. This method
3572is a utility provided for use with stores that store data embedded in
3573topic text. Only valid on topics.
3574
3575Note: line endings must be normalised to \n *before* calling this method.
3576
3577=cut
3578
3579
# spent 772ms (76.6+695) within Foswiki::Meta::setEmbeddedStoreForm which was called 426 times, avg 1.81ms/call: # 426 times (76.6ms+695ms) by Foswiki::Store::VC::Store::readTopic at line 94 of /var/www/foswiki11/lib/Foswiki/Store/VC/Store.pm, avg 1.81ms/call
sub setEmbeddedStoreForm {
3580426840µs my ( $this, $text ) = @_;
3581426486µs426485µs _assertIsTopic($this) if DEBUG;
# spent 485µs making 426 calls to Assert::ASSERTS_OFF, avg 1µs/call
3582
3583426159µs my $format = $EMBEDDING_FORMAT_VERSION;
3584
3585 # head meta-data
35864263.85ms $text =~ s/^(%META:(TOPICINFO){(.*)}%\n)/
3587425823µs42560.2ms $this->_readMETA($1, $2, $3)/e
# spent 60.2ms making 425 calls to Foswiki::Meta::_readMETA, avg 142µs/call
3588 ; #NO THIS CANNOT BE /g - TOPICINFO is _only_ valid as the first line!
3589426745µs4262.81ms my $ti = $this->get('TOPICINFO');
# spent 2.81ms making 426 calls to Foswiki::Meta::get, avg 7µs/call
3590426184µs if ($ti) {
3591425297µs $format = $ti->{format} || 0;
3592
3593 # Make sure we update the topic format for when we save
3594425195µs $ti->{format} = $EMBEDDING_FORMAT_VERSION;
3595
3596 # Clean up SVN and other malformed rev nums. This can happen
3597 # when old code (e.g. old plugins) generated the meta.
3598425909µs4252.47ms $ti->{version} = Foswiki::Store::cleanUpRevID( $ti->{version} );
# spent 2.47ms making 425 calls to Foswiki::Store::cleanUpRevID, avg 6µs/call
3599425353µs $ti->{rev} = $ti->{version}; # not used, maintained for compatibility
3600425744µs3411.08ms $ti->{reprev} = Foswiki::Store::cleanUpRevID( $ti->{reprev} )
# spent 1.08ms making 341 calls to Foswiki::Store::cleanUpRevID, avg 3µs/call
3601 if defined $ti->{reprev};
3602 }
3603 else {
3604
3605 #defaults..
3606 }
3607
3608 # Other meta-data
360942695µs my $endMeta = 0;
36104262.02ms if ( $format !~ /^[\d.]+$/ || $format < 1.1 ) {
36114124µs require Foswiki::Compatibility;
36124125µs if (
3613 $text =~ s/^%META:([^{]+){(.*)}%\n/
36141420µs14585µs Foswiki::Compatibility::readSymmetricallyEncodedMETA(
# spent 585µs making 14 calls to Foswiki::Compatibility::readSymmetricallyEncodedMETA, avg 42µs/call
3615143µs $this, $1, $2 ); ''/gem
3616 )
3617 {
3618 $endMeta = 1;
3619 }
3620 }
3621 else {
362242220.9ms if (
3623 $text =~ s/^(%META:([^{]+){(.*)}%\n)/
362481736.47ms if ($2 ne 'TOPICINFO') {
3625 #TOPICINFO is only valid on the first line
3626817310.8ms8173625ms $this->_readMETA($1, $2, $3)
# spent 625ms making 8173 calls to Foswiki::Meta::_readMETA, avg 76µs/call
3627 } else {
3628 $1
3629 }/gem
3630 )
3631 {
3632 $endMeta = 1;
3633 }
3634 }
3635
3636 # eat the extra newline put in to separate text from tail meta-data
3637426947µs $text =~ s/\n$//s if $endMeta;
3638
3639 # If there is no meta data then convert from old format
3640426976µs4262.66ms if ( !$this->count('TOPICINFO') ) {
# spent 2.66ms making 426 calls to Foswiki::Meta::count, avg 6µs/call
3641
3642 # The T-word string must remain unchanged for the compatibility
364314µs if ( $text =~ /<!--TWikiAttachment-->/ ) {
3644 require Foswiki::Compatibility;
3645 $text = Foswiki::Compatibility::migrateToFileAttachmentMacro(
3646 $this->{_session}, $this, $text );
3647 }
3648
3649 # The T-word string must remain unchanged for the compatibility
365014µs if ( $text =~ /<!--TWikiCat-->/ ) {
3651 require Foswiki::Compatibility;
3652 $text =
3653 Foswiki::Compatibility::upgradeCategoryTable( $this->{_session},
3654 $this->{_web}, $this->{_topic}, $this, $text );
3655 }
3656 }
3657 elsif ( $format eq '1.0beta' ) {
3658 require Foswiki::Compatibility;
3659
3660 # This format used live at DrKW for a few months
3661 # The T-word string must remain unchanged for the compatibility
3662 if ( $text =~ /<!--TWikiCat-->/ ) {
3663 $text =
3664 Foswiki::Compatibility::upgradeCategoryTable( $this->{_session},
3665 $this->{_web}, $this->{_topic}, $this, $text );
3666 }
3667 Foswiki::Compatibility::upgradeFrom1v0beta( $this->{_session}, $this );
3668 if ( $this->count('TOPICMOVED') ) {
3669 my $moved = $this->get('TOPICMOVED');
3670 $this->put( 'TOPICMOVED', $moved );
3671 }
3672 }
3673
3674426849µs if ( $format !~ /^[\d.]+$/ || $format < 1.1 ) {
3675
3676 # compatibility; topics version 1.0 and earlier equivalenced tab
3677 # with three spaces. Respect that.
36784202µs $text =~ s/\t/ /g;
3679 }
3680
36814262.01ms $this->{_text} = $text;
3682}
3683
3684=begin TML
3685
3686---++ ObjectMethod isValidEmbedding($macro, \%args) -> $boolean
3687
3688Test that the arguments defined in =\%args= are sufficient to satisfy the
3689requirements of the embeddable meta-data given by =$macro=. For example,
3690=isValidEmbedding('FILEATTACHMENT', $args)= will only succeed if $args contains
3691at least =name=, =date=, =user= and =attr= fields. Note that extra fields are
3692simply ignored (unless they are explicitly excluded).
3693
3694If the macro is not registered for validation, then it will be ignored.
3695
3696If the embedding is not valid, then $Foswiki::Meta::reason is set with a
3697message explaining why.
3698
3699=cut
3700
3701
# spent 63.0ms within Foswiki::Meta::isValidEmbedding which was called 8598 times, avg 7µs/call: # 8598 times (63.0ms+0s) by Foswiki::Meta::_readMETA at line 3743, avg 7µs/call
sub isValidEmbedding {
370285984.57ms my ( $this, $macro, $args ) = @_;
3703
370485982.62ms my $validate = $VALIDATE{$macro};
370585982.51ms return 1 unless $validate; # not validated
3706
370782042.28ms if ( defined $validate->{function} ) {
3708 unless ( &{ $validate->{function} }( $macro, $args ) ) {
3709 $reason = "\%META:$macro validation failed";
3710 return 0;
3711 }
3712
3713 # Fall through to check other constraints
3714 }
3715
37168204807µs my %allowed;
371782043.52ms if ( defined $validate->{require} ) {
371874209.28ms map { $allowed{$_} = 1 } @{ $validate->{require} };
3719742010.6ms foreach my $p ( @{ $validate->{require} } ) {
3720144967.68ms if ( !defined $args->{$p} ) {
3721 $reason = "$p was missing from \%META:$macro";
3722 return 0;
3723 }
3724 }
3725 }
3726
372782042.30ms if ( defined $validate->{allow} ) {
37287842.42ms map { $allowed{$_} = 1 } @{ $validate->{allow} };
37297841.71ms foreach my $arg ( keys %$args ) {
373027271.44ms if ( !$allowed{$arg} ) {
3731 $reason = "$arg was present in \%META:$macro";
3732 return 0;
3733 }
3734 }
3735 }
3736
3737820431.4ms return 1;
3738}
3739
3740
# spent 685ms (135+550) within Foswiki::Meta::_readMETA which was called 8598 times, avg 80µs/call: # 8173 times (126ms+499ms) by Foswiki::Meta::setEmbeddedStoreForm at line 3626, avg 76µs/call # 425 times (9.23ms+51.0ms) by Foswiki::Meta::setEmbeddedStoreForm at line 3587, avg 142µs/call
sub _readMETA {
3741859810.6ms my ( $this, $expr, $type, $args ) = @_;
374285988.04ms8598282ms my $keys = _readKeyValues($args);
# spent 282ms making 8598 calls to Foswiki::Meta::_readKeyValues, avg 33µs/call
3743859810.7ms859863.0ms if ( $this->isValidEmbedding( $type, $keys ) ) {
# spent 63.0ms making 8598 calls to Foswiki::Meta::isValidEmbedding, avg 7µs/call
3744859811.0ms7774187ms if ( defined( $keys->{name} ) ) {
# spent 187ms making 7774 calls to Foswiki::Meta::putKeyed, avg 24µs/call
3745
3746 # save it keyed if it has a name
3747 $this->putKeyed( $type, $keys );
3748 }
3749 else {
37508241.39ms82417.8ms $this->put( $type, $keys );
# spent 17.8ms making 824 calls to Foswiki::Meta::put, avg 22µs/call
3751 }
3752859829.4ms return '';
3753 }
3754 else {
3755 return $expr;
3756 }
3757}
3758
3759# STATIC Build a hash by parsing name=value comma separated pairs
3760# SMELL: duplication of Foswiki::Attrs, using a different
3761# system of escapes :-(
3762
# spent 282ms (223+59.0) within Foswiki::Meta::_readKeyValues which was called 8598 times, avg 33µs/call: # 8598 times (223ms+59.0ms) by Foswiki::Meta::_readMETA at line 3742, avg 33µs/call
sub _readKeyValues {
376385983.85ms my ($args) = @_;
37648598946µs my %res;
3765
3766 # Format of data is name='value' name1='value1' [...]
3767859861.1ms $args =~ s/\s*([^=]+)="([^"]*)"/
37683092055.7ms3092059.0ms $res{$1} = dataDecode( $2 ), ''/ge;
# spent 59.0ms making 30920 calls to Foswiki::Meta::dataDecode, avg 2µs/call
3769
3770859831.8ms return \%res;
3771}
3772
3773=begin TML
3774
3775---++ StaticMethod dataEncode( $uncoded ) -> $coded
3776
3777Encode meta-data field values, escaping out selected characters.
3778The encoding is chosen to avoid problems with parsing the attribute
3779values in embedded meta-data, while minimising the number of
3780characters encoded so searches can still work (fairly) sensibly.
3781
3782The encoding has to be exported because Foswiki (and plugins) use
3783encoded field data in other places e.g. RDiff, mainly as a shorthand
3784for the properly parsed meta object. Some day we may be able to
3785eliminate that....
3786
3787=cut
3788
3789
# spent 550µs within Foswiki::Meta::dataEncode which was called 392 times, avg 1µs/call: # 392 times (550µs+0s) by Foswiki::Meta::_writeKeyValue at line 3510, avg 1µs/call
sub dataEncode {
379039286µs my $datum = shift;
3791
3792392144µs $datum =~ s/([%"\r\n{}])/'%'.sprintf('%02x',ord($1))/ge;
3793392815µs return $datum;
3794}
3795
3796=begin TML
3797
3798---++ StaticMethod dataDecode( $encoded ) -> $decoded
3799
3800Decode escapes in a string that was encoded using dataEncode
3801
3802The encoding has to be exported because Foswiki (and plugins) use
3803encoded field data in other places e.g. RDiff, mainly as a shorthand
3804for the properly parsed meta object. Some day we may be able to
3805eliminate that....
3806
3807=cut
3808
3809
# spent 59.0ms within Foswiki::Meta::dataDecode which was called 30920 times, avg 2µs/call: # 30920 times (59.0ms+0s) by Foswiki::Meta::_readKeyValues at line 3768, avg 2µs/call
sub dataDecode {
38103092014.5ms my $datum = shift;
3811
38123092011.4ms $datum =~ s/%([\da-f]{2})/chr(hex($1))/gei;
381330920125ms return $datum;
3814}
3815
3816117µs1;
3817__END__