← Index
NYTProf Performance Profile   « block view • line view • sub view »
For /usr/local/src/github.com/foswiki/core/bin/view
  Run on Sun Dec 4 17:17:59 2011
Reported on Sun Dec 4 17:27:05 2011

Filename/usr/local/src/github.com/foswiki/core/lib/Foswiki/Meta.pm
StatementsExecuted 67477 statements in 285ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
5016241135.6ms35.6msFoswiki::Meta::::webFoswiki::Meta::web
5056241235.1ms35.1msFoswiki::Meta::::topicFoswiki::Meta::topic
4091125.1ms52.9msFoswiki::Meta::::_readKeyValuesFoswiki::Meta::_readKeyValues
11315520.9ms183sFoswiki::Meta::::expandMacrosFoswiki::Meta::expandMacros (recurses: max depth 1, inclusive time 2.42s)
4091118.4ms18.4msFoswiki::Meta::::isValidEmbeddingFoswiki::Meta::isValidEmbedding
15221116.7ms19.3msFoswiki::Meta::::dataDecodeFoswiki::Meta::dataDecode
3391115.6ms19.9msFoswiki::Meta::::putKeyedFoswiki::Meta::putKeyed
701113.4ms131msFoswiki::Meta::::setEmbeddedStoreFormFoswiki::Meta::setEmbeddedStoreForm
4092112.7ms108msFoswiki::Meta::::_readMETAFoswiki::Meta::_readMETA
2470418.75ms8.75msFoswiki::Meta::::CORE:substcontFoswiki::Meta::CORE:substcont (opcode)
15915107.45ms9.15msFoswiki::Meta::::newFoswiki::Meta::new
561086.58ms32.8sFoswiki::Meta::::loadFoswiki::Meta::load
2130516.44ms6.44msFoswiki::Meta::::CORE:substFoswiki::Meta::CORE:subst (opcode)
264965.22ms6.29msFoswiki::Meta::::getFoswiki::Meta::get
1114.64ms1.36sFoswiki::Meta::::renderFormForDisplayFoswiki::Meta::renderFormForDisplay
70324.45ms93.8sFoswiki::Meta::::loadVersionFoswiki::Meta::loadVersion
70113.18ms3.94msFoswiki::Meta::::putFoswiki::Meta::put
145442.82ms60.8sFoswiki::Meta::::getPreferenceFoswiki::Meta::getPreference
56882.50ms60.9sFoswiki::Meta::::haveAccessFoswiki::Meta::haveAccess
97222.28ms15.5msFoswiki::Meta::::existsInStoreFoswiki::Meta::existsInStore
91772.21ms61.0sFoswiki::Meta::::textFoswiki::Meta::text
132631.61ms1.61msFoswiki::Meta::::getPathFoswiki::Meta::getPath
60431.34ms1.55msFoswiki::Meta::::findFoswiki::Meta::find
167211.22ms1.22msFoswiki::Meta::::addDependencyFoswiki::Meta::addDependency
1111.19ms3.00msFoswiki::Meta::::BEGIN@119Foswiki::Meta::BEGIN@119
70111.19ms1.42msFoswiki::Meta::::countFoswiki::Meta::count
2922973µs1.63msFoswiki::Meta::::getRevisionInfoFoswiki::Meta::getRevisionInfo
111798µs1.07msFoswiki::Meta::::BEGIN@120Foswiki::Meta::BEGIN@120
14641773µs773µsFoswiki::Meta::::CORE:matchFoswiki::Meta::CORE:match (opcode)
3311598µs2.63msFoswiki::Meta::::getContainerFoswiki::Meta::getContainer
9576478µs478µsFoswiki::Meta::::sessionFoswiki::Meta::session
522225µs191msFoswiki::Meta::::renderTMLFoswiki::Meta::renderTML
922165µs196µsFoswiki::Meta::::getLoadedRevFoswiki::Meta::getLoadedRev
21182µs6.97msFoswiki::Meta::::getEmbeddedStoreFormFoswiki::Meta::getEmbeddedStoreForm
22178µs621msFoswiki::Meta::::getRevisionHistoryFoswiki::Meta::getRevisionHistory
11174µs85µsFoswiki::Meta::::unloadFoswiki::Meta::unload
11154µs170msFoswiki::Meta::::eachTopicFoswiki::Meta::eachTopic
11146µs1.66sFoswiki::Meta::::queryFoswiki::Meta::query
11141µs131µsFoswiki::Meta::::finishFoswiki::Meta::finish
11138µs1.35sFoswiki::Meta::::__ANON__[:1739]Foswiki::Meta::__ANON__[:1739]
11128µs32µsFoswiki::Meta::::latestIsLoadedFoswiki::Meta::latestIsLoaded
11124µs48µsFoswiki::Meta::::getFormNameFoswiki::Meta::getFormName
11124µs31µsFoswiki::Meta::::BEGIN@115Foswiki::Meta::BEGIN@115
11122µs106µsFoswiki::Meta::::BEGIN@546Foswiki::Meta::BEGIN@546
11116µs52µsFoswiki::Meta::::BEGIN@118Foswiki::Meta::BEGIN@118
11116µs394µsFoswiki::Meta::::BEGIN@117Foswiki::Meta::BEGIN@117
11115µs33µsFoswiki::Meta::::BEGIN@116Foswiki::Meta::BEGIN@116
0000s0sFoswiki::Meta::::__ANON__[:1748]Foswiki::Meta::__ANON__[:1748]
0000s0sFoswiki::Meta::::__ANON__[:1798]Foswiki::Meta::__ANON__[:1798]
0000s0sFoswiki::Meta::::__ANON__[:1802]Foswiki::Meta::__ANON__[:1802]
0000s0sFoswiki::Meta::::__ANON__[:1902]Foswiki::Meta::__ANON__[:1902]
0000s0sFoswiki::Meta::::__ANON__[:1905]Foswiki::Meta::__ANON__[:1905]
0000s0sFoswiki::Meta::::__ANON__[:2029]Foswiki::Meta::__ANON__[:2029]
0000s0sFoswiki::Meta::::__ANON__[:2033]Foswiki::Meta::__ANON__[:2033]
0000s0sFoswiki::Meta::::__ANON__[:2177]Foswiki::Meta::__ANON__[:2177]
0000s0sFoswiki::Meta::::__ANON__[:2183]Foswiki::Meta::__ANON__[:2183]
0000s0sFoswiki::Meta::::__ANON__[:2231]Foswiki::Meta::__ANON__[:2231]
0000s0sFoswiki::Meta::::__ANON__[:2234]Foswiki::Meta::__ANON__[:2234]
0000s0sFoswiki::Meta::::__ANON__[:2294]Foswiki::Meta::__ANON__[:2294]
0000s0sFoswiki::Meta::::__ANON__[:2297]Foswiki::Meta::__ANON__[:2297]
0000s0sFoswiki::Meta::::__ANON__[:2772]Foswiki::Meta::__ANON__[:2772]
0000s0sFoswiki::Meta::::__ANON__[:2775]Foswiki::Meta::__ANON__[:2775]
0000s0sFoswiki::Meta::::__ANON__[:2990]Foswiki::Meta::__ANON__[:2990]
0000s0sFoswiki::Meta::::__ANON__[:2996]Foswiki::Meta::__ANON__[:2996]
0000s0sFoswiki::Meta::::__ANON__[:3069]Foswiki::Meta::__ANON__[:3069]
0000s0sFoswiki::Meta::::__ANON__[:3075]Foswiki::Meta::__ANON__[:3075]
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::::eachWebFoswiki::Meta::eachWeb
0000s0sFoswiki::Meta::::expandNewTopicFoswiki::Meta::expandNewTopic
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::::hasAttachmentFoswiki::Meta::hasAttachment
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
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
93API version $Date$ (revision $Rev$)
94
95*Since* _date_ indicates where functions or parameters have been added since
96the baseline of the API (Foswiki release 4.2.3). The _date_ indicates the
97earliest date of a Foswiki release that will support that function or
98parameter.
99
100*Deprecated* _date_ indicates where a function or parameters has been
101[[http://en.wikipedia.org/wiki/Deprecation][deprecated]]. Deprecated
102functions will still work, though they should
103_not_ be called in new plugins and should be replaced in older plugins
104as soon as possible. Deprecated parameters are simply ignored in Foswiki
105releases after _date_.
106
107*Until* _date_ indicates where a function or parameter has been removed.
108The _date_ indicates the latest date at which Foswiki releases still supported
109the function or parameter.
110
111=cut
112
113package Foswiki::Meta;
114
115244µs238µs
# spent 31µs (24+7) within Foswiki::Meta::BEGIN@115 which was called: # once (24µs+7µs) by Foswiki::BEGIN@607 at line 115
use strict;
# spent 31µs making 1 call to Foswiki::Meta::BEGIN@115 # spent 7µs making 1 call to strict::import
116254µs250µs
# spent 33µs (15+18) within Foswiki::Meta::BEGIN@116 which was called: # once (15µs+18µs) by Foswiki::BEGIN@607 at line 116
use warnings;
# spent 33µs making 1 call to Foswiki::Meta::BEGIN@116 # spent 18µs making 1 call to warnings::import
117247µs2771µs
# spent 394µs (16+378) within Foswiki::Meta::BEGIN@117 which was called: # once (16µs+378µs) by Foswiki::BEGIN@607 at line 117
use Error qw(:try);
# spent 394µs making 1 call to Foswiki::Meta::BEGIN@117 # spent 378µs making 1 call to Error::import
118246µs288µs
# spent 52µs (16+36) within Foswiki::Meta::BEGIN@118 which was called: # once (16µs+36µs) by Foswiki::BEGIN@607 at line 118
use Assert;
# spent 52µs making 1 call to Foswiki::Meta::BEGIN@118 # spent 36µs making 1 call to Assert::import
1192185µs23.89ms
# spent 3.00ms (1.19+1.80) within Foswiki::Meta::BEGIN@119 which was called: # once (1.19ms+1.80ms) by Foswiki::BEGIN@607 at line 119
use Errno 'EINTR';
# spent 3.00ms making 1 call to Foswiki::Meta::BEGIN@119 # spent 898µs making 1 call to Exporter::import
12021.87ms11.07ms
# spent 1.07ms (798µs+272µs) within Foswiki::Meta::BEGIN@120 which was called: # once (798µs+272µs) by Foswiki::BEGIN@607 at line 120
use Foswiki::Serialise ();
# spent 1.07ms making 1 call to Foswiki::Meta::BEGIN@120
121
122#use Foswiki::Iterator::NumberRangeIterator;
123
12411µsour $reason;
12512µsour $VERSION = '$Rev$';
126
127# Version for the embedding format (increment when embedding format changes)
12812µsour $EMBEDDING_FORMAT_VERSION = 1.1;
129
130# defaults for truncation of summary text
13111µsour $SUMMARY_TMLTRUNC = 162;
13211µsour $SUMMARY_MINTRUNC = 16;
13312µsour $SUMMARY_ELLIPSIS = '<b>&hellip;</b>'; # Google style
134
135# the number of characters either side of a search term
13611µsour $SUMMARY_DEFAULT_CONTEXT = 30;
137
138# max number of lines in a summary (best to keep it even)
13911µsour $CHANGES_SUMMARY_LINECOUNT = 6;
14011µsour $CHANGES_SUMMARY_PLAINTRUNC = 70;
141
142=begin TML
143
144PUBLIC %VALIDATE;
145
146META:x validation. This hash maps from META: names to the type record
147registered by registerMETA. See registerMETA for more information on what
148these records contain.
149
150_default is set on base meta-data types (those not added by
151Foswiki::Func::registerMETA) to differentiate the minimum required
152meta-data and that added by extensions.
153
154=cut
155
156130µsour %VALIDATE = (
157 TOPICINFO => {
158 allow => [
159 qw( author version date format reprev
160 rev comment encoding )
161 ],
162 _default => 1,
163 alias => 'info',
164 },
165 CREATEINFO => {
166 allow => [
167 qw( author version date format reprev
168 rev comment encoding )
169 ],
170
171 # _default => 1,
172 # alias => 'createinfo',
173 },
174 TOPICMOVED => {
175 require => [qw( from to by date )],
176 _default => 1,
177 alias => 'moved',
178 },
179
180 # Special case, see Item2554; allow an empty TOPICPARENT, as this was
181 # erroneously generated at some point in the past
182 TOPICPARENT => {
183 allow => [qw( name )],
184 _default => 1,
185 alias => 'parent',
186 },
187 FILEATTACHMENT => {
188 require => [qw( name )],
189 other => [
190 qw( version path size date user
191 comment attr )
192 ],
193 _default => 1,
194 alias => 'attachments',
195 many => 1,
196 },
197 FORM => {
198 require => [qw( name )],
199 _default => 1,
200 alias => 'form',
201 },
202 FIELD => {
203 require => [qw( name value )],
204 other => [qw( title )],
205 _default => 1,
206 alias => 'fields',
207 many => 1,
208 },
209 PREFERENCE => {
210 require => [qw( name value )],
211 other => [qw( type )],
212 _default => 1,
213 alias => 'preferences',
214 many => 1,
215 },
216 VERSIONS => {
217
218 # In trad text based data store, this does not occur in the
219 # topic text, but is pulled on demand during queries
220 alias => 'versions',
221 }
222);
223
224816µsour %aliases =
225917µs map { $VALIDATE{$_}->{alias} => "META:$_" }
226138µs grep { $VALIDATE{$_}->{alias} } keys %VALIDATE;
227
22834µsour %isArrayType =
229914µs map { $_ => 1 }
230121µs grep { $VALIDATE{$_}->{many} } keys %VALIDATE;
231
232=begin TML
233
234---++ StaticMethod registerMETA($name, %syntax)
235
236Foswiki supports embedding meta-data into topics. For example,
237
238=%<nop>META:BOOK{title="Transit" author="Edmund Cooper" isbn="0-571-05724-1"}%=
239
240This meta-data is validated when it is read from the store. Meta-data
241that is not registered, or doesn't pass validation, is ignored. This
242function allows you to register a new META datum, passing the name in
243=$name=. =%syntax= contains information about the syntax and semantics of
244the tag.
245
246The following entries are supported in =%syntax=
247
248=many=>1=. By default meta-data are single valued i.e. can only occur once
249in a topic. If you require the meta-data to be repeated many times (like
250META:FIELD and META:ATTACHMENT) then you must set this option. For example,
251to declare a many-valued =BOOK= meta-data type:
252<verbatim>
253registerMeta('BOOK', many => 1)
254</verbatim>
255
256=require=>[]= is used to check that a list of named parameters are present on
257the tag. For example,
258<verbatim>
259registerMETA('BOOK', require => [ 'title', 'author' ]);
260</verbatim>
261can be used to check that both =title= and =author= are present.
262
263=allow=>[]= lets you specify other optional parameters that are allowed
264on the tag. If you specify =allow= then the validation will fail if the
265tag contains any parameters that are _not_ in the =allow= or =require= lists.
266If you don't specify =allow= then all parameters will be allowed.
267
268=require= and =allow= only verify the *presence* of parameters, and
269not their *values*.
270
271=other=[]= lets you declare other legal parameters, and is provided
272mainly to support the initialisation of DB schema. It it is like
273=allow= except that it doesn't imply any exclusion of META that contains
274unallowed params.
275
276=function=>\&fn= causes the function =fn= to be called when the
277datum is encountered when reading a topic, passing in the name of the
278macro and the argument hash. The function must return a non-zero/undef
279value if the tag is acceptable, or 0 otherwise. For example:
280<verbatim>
281registerMETA('BOOK', function => sub {
282 my ($name, $args) = @_;
283 # $name will be BOOK
284 return isValidTitle($args->{title});
285}
286</verbatim>
287can be used to check that =%META:BOOK{}= contains a valid title.
288
289Checks are cumulative, so if you:
290<verbatim>
291registerMETA('BOOK',
292 function => \&checkParameters,
293 require => [ 'title' ],
294 allow => [ 'author', 'isbn' ]);
295</verbatim>
296then all these conditions will be tested. Note that =require= and =allow=
297are tested _after_ =function= is called, to give the function a chance to
298rewrite the parameter list.
299
300If no checker is registered for a META tag, then it will automatically
301be accepted into the topic meta-data.
302
303=alias=>'name'= lets you set an alias for the datum that will be added to
304the query language. For example, =alias=>'info'= is used to alias
305'META:TOPICINFO' in queries.
306<verbatim>
307registerMeta('BOOK', alias => 'book', many => 1)
308</verbatim>
309This lets you use syntax such as =books[author='Anais Nin']= in queries.
310See QuerySearch for more on aliases.
311
312=cut
313
314sub registerMETA {
315 my ( $name, %check ) = @_;
316 $VALIDATE{$name} = \%check;
317 $aliases{ $check{alias} } = "META:$name" if $check{alias};
318 $isArrayType{$name} = $check{many};
319}
320
321############# GENERIC METHODS #############
322
323=begin TML
324
325---++ ClassMethod new($session, $web, $topic [, $text])
326 * =$session= - a Foswiki object (e.g. =$Foswiki::Plugins::SESSION=)
327 * =$web=, =$topic= - the pathname of the object. If both are undef,
328 this object is a handle for the root container. If $topic is undef,
329 it is the handle to a web. Otherwise it's a handle to a topic.
330 * $text - optional raw text, which may include embedded meta-data. Will
331 be passed to =setEmbeddedStoreForm= to initialise the object. Only valid
332 if =$web= and =$topic= are defined.
333Construct a new, unloaded object. This method creates lightweight
334handles for store objects; the full content of the actual object will
335*not* be loaded. If you need to interact with the existing content of
336the stored object, use the =load= method to load the content.
337
338---++ ClassMethod new($prototype)
339
340Construct a new, unloaded object, using the session, web and topic in the
341prototype object (which must be type Foswiki::Meta).
342
343=cut
344
345
# spent 9.15ms (7.45+1.70) within Foswiki::Meta::new which was called 159 times, avg 58µs/call: # 55 times (2.45ms+582µs) by Foswiki::Meta::load at line 454, avg 55µs/call # 48 times (2.10ms+514µs) by Foswiki::Prefs::_getBackend at line 139 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs.pm, avg 55µs/call # 33 times (1.69ms+346µs) by Foswiki::Meta::getContainer at line 677, avg 62µs/call # 6 times (315µs+67µs) by Foswiki::REVINFO at line 27 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/REVINFO.pm, avg 64µs/call # 4 times (191µs+47µs) by Foswiki::Render::_renderNonExistingWikiWord at line 702 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm, avg 59µs/call # 2 times (98µs+24µs) by Foswiki::_renderZone at line 3409 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm, avg 61µs/call # 2 times (92µs+22µs) by Foswiki::Search::formatResults at line 704 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Search.pm, avg 57µs/call # 2 times (83µs+22µs) by Foswiki::Func::expandCommonVariables at line 2472 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm, avg 53µs/call # once (84µs+13µs) by Foswiki::Store::Interfaces::QueryAlgorithm::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm:228] at line 216 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm # once (74µs+12µs) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 133 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm # once (68µs+11µs) by Foswiki::Search::searchWeb at line 251 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Search.pm # once (62µs+11µs) by Foswiki::Form::new at line 107 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Form.pm # once (49µs+11µs) by Foswiki::Store::Interfaces::QueryAlgorithm::query at line 85 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm # once (42µs+11µs) by Foswiki::Search::formatResults at line 858 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Search.pm # once (40µs+10µs) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 71 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm
sub new {
346159401µs my ( $class, $session, $web, $topic, $text ) = @_;
347
3481591.64ms159560µs if ( $session->isa('Foswiki::Meta') ) {
# spent 560µs making 159 calls to UNIVERSAL::isa, avg 4µs/call
349
350 # Prototype
351 ASSERT( !defined($web) && !defined($topic) && !defined($text) )
352 if DEBUG;
353 return $class->new( $session->session, $session->web, $session->topic );
354 }
355
356159863µs my $this = bless(
357 {
358 _session => $session,
359
360 # Index keyed on top level type mapping entry names to their
361 # index within the data array.
362 _indices => undef,
363 },
364 ref($class) || $class
365 );
366
367 # Normalise web path (replace [./]+ with /)
368159445µs if ( defined $web ) {
369159796µs159604µs ASSERT( UNTAINTED($web), 'web is tainted' ) if DEBUG;
# spent 604µs making 159 calls to Assert::ASSERTS_OFF, avg 4µs/call
370159361µs $web =~ tr#/.#/#s;
371 }
372
373 # Note: internal fields are prepended with _. All uppercase
374 # fields will be assumed to be meta-data.
375
376159422µs $this->{_web} = $web;
377
378159743µs159539µs ASSERT( UNTAINTED($topic), 'topic is tainted' )
# spent 539µs making 159 calls to Assert::ASSERTS_OFF, avg 3µs/call
379 if ( DEBUG && defined $topic );
380
381159349µs $this->{_topic} = $topic;
382
383 #print STDERR "--new Meta($web, ".($topic||'undef').")\n";
384 #$this->{_text} = undef; # topics only
385
386 # Preferences cache object. We store a pointer, rather than looking
387 # up the name each time, because we want to be able to invalidate the
388 # loaded preferences if this object is loaded.
389 #$this->{_preferences} = undef;
390
391159405µs $this->{FILEATTACHMENT} = [];
392
393159210µs if ( defined $text ) {
394
395 # User supplied topic body forces us to consider this as the
396 # latest rev
397 ASSERT( defined($web), 'web is not defined' ) if DEBUG;
398 ASSERT( defined($topic), 'topic is not defined' ) if DEBUG;
399 $this->setEmbeddedStoreForm($text);
400 $this->{_latestIsLoaded} = 1;
401 }
402
403159943µs return $this;
404}
405
406=begin TML
407
408---++ ClassMethod load($session, $web, $topic, $rev)
409
410This constructor will load (or otherwise fetch) the meta-data for a
411named web/topic.
412 * =$rev= - revision to load. If undef, 0, '' or > max available rev, will
413 load the latest rev. If the revision is in range but does not exist,
414 then will return an unloaded meta object (getLoadedRev() will be undef)
415
416This method is functionally identical to:
417<verbatim>
418$this = Foswiki::Meta->new( $session, $web, $topic );
419$this->loadVersion( $rev );
420</verbatim>
421
422WARNING: see notes on revision numbers under =getLoadedRev=.
423
424---++ ObjectMethod load($rev) -> $metaObject
425
426Load an unloaded meta-data object with a given version of the data.
427Once loaded, the object is locked to that revision.
428
429 * =$rev= - revision to load. If undef, 0, '' or > max available rev, will
430 load the latest rev. If the revision is in range but does not exist,
431 then will return an unloaded meta object (getLoadedRev() will be undef)
432
433WARNING: see notes on revision numbers under =getLoadedRev=
434
435
436TODO: this is insane. load() can fail - but it will give you a seemingly fine Meta object anyway.
437
438=cut
439
440
# spent 32.8s (6.58ms+32.8) within Foswiki::Meta::load which was called 56 times, avg 586ms/call: # 23 times (2.29ms+2.81s) by Foswiki::MetaCache::addMeta at line 137 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/MetaCache.pm, avg 122ms/call # 20 times (2.55ms+6.09s) by Foswiki::INCLUDE at line 205 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm, avg 305ms/call # 3 times (413µs+409ms) by Foswiki::Templates::_readTemplateFile at line 516 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Templates.pm, avg 136ms/call # 2 times (381µs+20.3s) by Foswiki::Func::readTopicText at line 3448 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm, avg 10.1s/call # 2 times (279µs+3.16s) by Foswiki::Func::readTopic at line 1531 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm, avg 1.58s/call # 2 times (196µs+4.66ms) by Foswiki::Users::TopicUserMapping::eachGroupMember at line 650 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm, avg 2.43ms/call # once (126µs+25.2ms) by Foswiki::Func::checkAccessPermission at line 1363 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm # once (124µs+5.98ms) by Foswiki::UI::View::view at line 115 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm # once (124µs+5.90ms) by Foswiki::QUERY at line 33 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/QUERY.pm # once (92µs+5.88ms) by Foswiki::Render::renderFORMFIELD at line 1029 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm
sub load {
44156140µs my $proto = shift;
4425671µs my $this;
4435676µs my $rev;
444
44556164µs if ( ref($proto) ) {
446
447 # Existing unloaded object
44814µs13µs ASSERT( !$this->{_loadedRev} ) if DEBUG;
# spent 3µs making 1 call to Assert::ASSERTS_OFF
44912µs $this = $proto;
45012µs $rev = shift;
451 }
452 else {
45355265µs ( my $session, my $web, my $topic, $rev ) = @_;
45455347µs553.04ms $this = $proto->new( $session, $web, $topic );
# spent 3.04ms making 55 calls to Foswiki::Meta::new, avg 55µs/call
455 }
456
45756106µs my $session = $this->{_session};
45856704µs1119.00ms if ( defined( $this->topic )
# spent 8.57ms making 55 calls to Foswiki::Meta::existsInStore, avg 156µs/call # spent 425µs making 56 calls to Foswiki::Meta::topic, avg 8µs/call
459 and ( not defined($rev) )
460 and $this->existsInStore() )
461 {
462
463#SVEN: sadly, Item10805 shows that the metacache is not yet multi-user safe, and as the Groups code in TopicUserMapping changes to user=admin, we can't use it here
464#which makes it clear I need to write a full cache validation set of tests for MetaCache
465#TODO: need to extract the metacache from search, and extract the additional derived info from it too
466#TODO: this mess is because the Listeners cannot assign a cached meta object to an already existing unloaded meta
467# which in Sven's opinion means we need to invert things better. (I get ~10% (.2S on 2S req's) speedup on simpler SEARCH topics doing reuse)
468551.50ms27527.7ms my $m =
# spent 24.2ms making 55 calls to Foswiki::search, avg 439µs/call # spent 2.14ms making 55 calls to Foswiki::MetaCache::getMeta, avg 39µs/call # spent 571µs making 55 calls to Foswiki::Search::metacache, avg 10µs/call # spent 428µs making 55 calls to Foswiki::Meta::web, avg 8µs/call # spent 391µs making 55 calls to Foswiki::Meta::topic, avg 7µs/call
469 $session->search->metacache->getMeta( $this->web, $this->topic );
470
471#print STDERR "metacache->getMeta ".join(',', ( $this->web, $this->topic, ref($m) ))."\n";
4725599µs return $m if ( defined($m) );
473 }
474
47556281µs56205µs ASSERT( not( $this->{_latestIsLoaded} ) ) if DEBUG;
# spent 205µs making 56 calls to Assert::ASSERTS_OFF, avg 4µs/call
476
47756416µs5632.8s my $loadedRev = $this->loadVersion($rev);
# spent 32.8s making 56 calls to Foswiki::Meta::loadVersion, avg 585ms/call
478
47956188µs if ( not defined($loadedRev) ) {
480 ASSERT( not defined( $this->{_loadedRev} ) ) if DEBUG;
481
482#_latestIsloaded is mostly undef / 0 when the topic is not ondisk, except Fn_SEARCH::verify_refQuery_ForkingSearch and friends
483 ASSERT( not( $this->{_latestIsLoaded} ) ) if DEBUG;
484 }
485 else {
486
48756294µs56211µs ASSERT( defined( $this->{_loadedRev} ) and ( $this->{_loadedRev} > 0 ) )
# spent 211µs making 56 calls to Assert::ASSERTS_OFF, avg 4µs/call
488 if DEBUG;
48956262µs56183µs ASSERT( defined( $this->{_latestIsLoaded} ) ) if DEBUG;
# spent 183µs making 56 calls to Assert::ASSERTS_OFF, avg 3µs/call
490 }
491
49256686µs return $this;
493}
494
495=begin TML
496
497---++ ObjectMethod unload()
498
499Return the object to an unloaded state. This method should be used
500with the greatest of care, as it resets the load state of the object,
501which may have surprising effects on other code that shares the object.
502
503=cut
504
505
# spent 85µs (74+11) within Foswiki::Meta::unload which was called: # once (74µs+11µs) by Foswiki::Meta::finish at line 534
sub unload {
50612µs my $this = shift;
50712µs $this->{_loadedRev} = undef;
50813µs $this->{_latestIsLoaded} = undef;
50912µs $this->{_text} = undef;
51017µs111µs $this->{_preferences}->finish() if defined $this->{_preferences};
# spent 11µs making 1 call to Foswiki::Prefs::TopicRAM::finish
51113µs undef $this->{_preferences};
51212µs $this->{_preferences} = undef;
513
514 # Unload meta-data
515213µs foreach my $type ( keys %{ $this->{_indices} } ) {
516230µs delete $this->{$type};
517 }
51818µs undef $this->{_indices};
519}
520
521=begin TML
522
523---++ ObjectMethod finish()
524Clean up the object, releasing any memory stored in it. Make sure this
525gets called before an object you have created goes out of scope.
526
527=cut
528
529# Note to developers; please undef *all* fields in the object explicitly,
530# whether they are references or not. That way this method is "golden
531# documentation" of the live fields in the object.
532
# spent 131µs (41+90) within Foswiki::Meta::finish which was called: # once (41µs+90µs) by Foswiki::Form::finish at line 154 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Form.pm
sub finish {
53312µs my $this = shift;
534115µs185µs $this->unload();
# spent 85µs making 1 call to Foswiki::Meta::unload
53514µs undef $this->{_web};
53613µs undef $this->{_topic};
53712µs undef $this->{_session};
538111µs14µs if (DEBUG) {
# spent 4µs making 1 call to Assert::ASSERTS_OFF
539
540 #someone keeps adding random references to Meta so to shake them out..
541 #if its an intentional ref to an object, please add it to the undef's above.
542
543#SMELL: Sven noticed during development that something is adding a $this->{store} to a meta obj - havn't found it yet
544#ASSERT(not defined($this->{store})) if DEBUG;
545
546219.4ms2191µs
# spent 106µs (22+85) within Foswiki::Meta::BEGIN@546 which was called: # once (22µs+85µs) by Foswiki::BEGIN@607 at line 546
use Scalar::Util qw(blessed);
# spent 106µs making 1 call to Foswiki::Meta::BEGIN@546 # spent 85µs making 1 call to Exporter::import
547 foreach my $key (%$this) {
548
549 #ASSERT(not defined(blessed($this->{$key})));
550 }
551 }
552}
553
554=begin TML
555
556---++ ObjectMethod session()
557
558Get the session (Foswiki) object associated with the object when
559it was created.
560
561=cut
562
563
# spent 478µs within Foswiki::Meta::session which was called 95 times, avg 5µs/call: # 33 times (149µs+0s) by Foswiki::If::OP_context::evaluate at line 32 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/If/OP_context.pm, avg 5µs/call # 28 times (169µs+0s) by Foswiki::Form::createField at line 311 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Form.pm, avg 6µs/call # 15 times (64µs+0s) by Foswiki::If::OP_dollar::evaluate at line 30 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/If/OP_dollar.pm, avg 4µs/call # 10 times (51µs+0s) by Foswiki::If::OP_defined::evaluate at line 27 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/If/OP_defined.pm, avg 5µs/call # 6 times (26µs+0s) by Foswiki::If::OP_istopic::evaluate at line 27 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/If/OP_istopic.pm, avg 4µs/call # 2 times (16µs+0s) by Foswiki::Meta::getEmbeddedStoreForm at line 3501, avg 8µs/call # once (4µs+0s) by Foswiki::Form::renderForDisplay at line 595 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Form.pm
sub session {
56495685µs return $_[0]->{_session};
565}
566
567=begin TML
568
569---++ ObjectMethod web([$name])
570 * =$name= - optional, change the web name in the object
571 * *Since* 28 Nov 2008
572Get/set the web name associated with the object.
573
574=cut
575
576
# spent 35.6ms within Foswiki::Meta::web which was called 5016 times, avg 7µs/call: # 2321 times (16.2ms+0s) by Foswiki::innerExpandMacros at line 2718 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm, avg 7µs/call # 1131 times (8.23ms+0s) by Foswiki::expandMacros at line 3238 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm, avg 7µs/call # 1131 times (7.87ms+0s) by Foswiki::expandMacros at line 3223 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm, avg 7µs/call # 73 times (526µs+0s) by Foswiki::Store::VC::Handler::new at line 72 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Handler.pm, avg 7µs/call # 55 times (428µs+0s) by Foswiki::Meta::load at line 468, avg 8µs/call # 34 times (288µs+0s) by Foswiki::Prefs::loadPreferences at line 230 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs.pm, avg 8µs/call # 34 times (235µs+0s) by Foswiki::Prefs::loadPreferences at line 234 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs.pm, avg 7µs/call # 33 times (246µs+0s) by Foswiki::Render::_handleWikiWord at line 729 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm, avg 7µs/call # 32 times (240µs+0s) by Foswiki::Render::_handleSquareBracketedLink at line 890 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm, avg 8µs/call # 28 times (220µs+0s) by Foswiki::Form::_parseFormDefinition at line 263 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Form.pm, avg 8µs/call # 20 times (173µs+0s) by Foswiki::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm:326] at line 226 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm, avg 9µs/call # 20 times (164µs+0s) by Foswiki::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm:326] at line 309 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm, avg 8µs/call # 20 times (155µs+0s) by Foswiki::Prefs::popTopicContext at line 309 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs.pm, avg 8µs/call # 20 times (155µs+0s) by Foswiki::INCLUDE at line 172 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm, avg 8µs/call # 20 times (153µs+0s) by Foswiki::INCLUDE at line 148 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm, avg 8µs/call # 20 times (148µs+0s) by Foswiki::INCLUDE at line 190 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm, avg 7µs/call # 6 times (48µs+0s) by Foswiki::REVINFO at line 13 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/REVINFO.pm, avg 8µs/call # 6 times (43µs+0s) by Foswiki::REVINFO at line 22 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/REVINFO.pm, avg 7µs/call # 5 times (47µs+0s) by Foswiki::Render::getRenderedVersion at line 1143 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm, avg 9µs/call # 3 times (22µs+0s) by Foswiki::Func::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm:573] at line 568 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm, avg 7µs/call # once (14µs+0s) by Foswiki::UI::View::view at line 203 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm # once (9µs+0s) by Foswiki::Render::renderFORMFIELD at line 1029 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm # once (8µs+0s) by Foswiki::FORMAT at line 30 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/FORMAT.pm # once (7µs+0s) by Foswiki::Render::renderParent at line 147 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm
sub web {
57750168.35ms my ( $this, $web ) = @_;
57850166.37ms $this->{_web} = $web if defined $web;
579501627.5ms return $this->{_web};
580}
581
582=begin TML
583
584---++ ObjectMethod topic([$name])
585 * =$name= - optional, change the topic name in the object
586 * *Since* 28 Nov 2008
587Get/set the topic name associated with the object.
588
589=cut
590
591
# spent 35.1ms within Foswiki::Meta::topic which was called 5056 times, avg 7µs/call: # 2321 times (15.7ms+0s) by Foswiki::innerExpandMacros at line 2719 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm, avg 7µs/call # 1131 times (7.98ms+0s) by Foswiki::expandMacros at line 3223 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm, avg 7µs/call # 1131 times (7.80ms+0s) by Foswiki::expandMacros at line 3239 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm, avg 7µs/call # 73 times (538µs+0s) by Foswiki::Store::VC::Handler::new at line 71 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Handler.pm, avg 7µs/call # 69 times (540µs+0s) by Foswiki::Prefs::loadPreferences at line 230 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs.pm, avg 8µs/call # 56 times (425µs+0s) by Foswiki::Meta::load at line 458, avg 8µs/call # 55 times (391µs+0s) by Foswiki::Meta::load at line 468, avg 7µs/call # 28 times (195µs+0s) by Foswiki::Form::_parseFormDefinition at line 263 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Form.pm, avg 7µs/call # 22 times (207µs+0s) by Foswiki::Search::formatResult at line 1135 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Search.pm, avg 9µs/call # 22 times (176µs+0s) by Foswiki::Search::formatResult at line 1225 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Search.pm, avg 8µs/call # 22 times (169µs+0s) by Foswiki::Search::formatResult at line 1165 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Search.pm, avg 8µs/call # 22 times (162µs+0s) by Foswiki::Search::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Search.pm:1000] at line 998 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Search.pm, avg 7µs/call # 20 times (145µs+0s) by Foswiki::Prefs::popTopicContext at line 309 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs.pm, avg 7µs/call # 20 times (144µs+0s) by Foswiki::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm:326] at line 226 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm, avg 7µs/call # 20 times (144µs+0s) by Foswiki::INCLUDE at line 149 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm, avg 7µs/call # 20 times (141µs+0s) by Foswiki::INCLUDE at line 190 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm, avg 7µs/call # 6 times (47µs+0s) by Foswiki::REVINFO at line 14 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/REVINFO.pm, avg 8µs/call # 6 times (41µs+0s) by Foswiki::REVINFO at line 22 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/REVINFO.pm, avg 7µs/call # 5 times (41µs+0s) by Foswiki::Render::getRenderedVersion at line 1143 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm, avg 8µs/call # 3 times (26µs+0s) by Foswiki::Func::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm:573] at line 568 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm, avg 9µs/call # once (11µs+0s) by Foswiki::UI::View::view at line 203 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm # once (8µs+0s) by Foswiki::Render::renderFORMFIELD at line 1029 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm # once (8µs+0s) by Foswiki::FORMAT at line 31 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/FORMAT.pm # once (7µs+0s) by Foswiki::Render::renderParent at line 147 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm
sub topic {
59250568.13ms my ( $this, $topic ) = @_;
59350566.36ms $this->{_topic} = $topic if defined $topic;
594505628.3ms return $this->{_topic};
595}
596
597=begin TML
598
599---++ ObjectMethod getPath() -> $objectpath
600
601Get the canonical content access path for the object. For example,
602a topic "MyTopic" in subweb "Subweb" of web "Myweb" will have an
603access path "Myweb/Subweb.MyTopic"
604
605=cut
606
607
# spent 1.61ms within Foswiki::Meta::getPath which was called 132 times, avg 12µs/call: # 69 times (842µs+0s) by Foswiki::Prefs::loadPreferences at line 222 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs.pm, avg 12µs/call # 48 times (575µs+0s) by Foswiki::Prefs::_getBackend at line 140 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs.pm, avg 12µs/call # 7 times (85µs+0s) by Foswiki::Render::renderFORMFIELD at line 1027 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm, avg 12µs/call # 5 times (68µs+0s) by Foswiki::Render::getAnchorNames at line 2203 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm, avg 14µs/call # 2 times (25µs+0s) by Foswiki::Form::renderForDisplay at line 621 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Form.pm, avg 12µs/call # once (11µs+0s) by Foswiki::Render::renderFORMFIELD at line 1040 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm
sub getPath {
608132201µs my $this = shift;
609132319µs my $path = $this->{_web};
610
611132165µs return '' unless $path;
612132351µs return $path unless $this->{_topic};
61398192µs $path .= '.' . $this->{_topic};
61498682µs return $path;
615}
616
617=begin TML
618
619---++ ObjectMethod isSessionTopic() -> $boolean
620Return true if this object refers to the session topic. The session
621topic is established from the path used to invoke Foswiki, for example
622".../view/Myweb/MyTopic" sets "Myweb.MyTopic" as the session topic.
623
624=cut
625
626sub isSessionTopic {
627 my $this = shift;
628 return 0
629 unless defined $this->{_web}
630 && defined $this->{_topic}
631 && defined $this->{_session}->{webName}
632 && defined $this->{_session}->{topicName};
633 return $this->{_web} eq $this->{_session}->{webName}
634 && $this->{_topic} eq $this->{_session}->{topicName};
635}
636
637=begin TML
638
639---++ ObjectMethod getPreference( $key ) -> $value
640
641Get a value for a preference defined *in* the object. Note that
642web preferences always inherit from parent webs, but topic preferences
643are strictly local to topics.
644
645Note that this is *not* the same as =Foswiki::Func::getPreferencesValue=,
646which is almost certainly what you want to call instead.
647
648=cut
649
650
# spent 60.8s (2.82ms+60.8) within Foswiki::Meta::getPreference which was called 145 times, avg 419ms/call: # 132 times (2.54ms+60.8s) by Foswiki::Access::TopicACLAccess::_getACL at line 203 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Access/TopicACLAccess.pm, avg 460ms/call # 10 times (171µs+72µs) by Foswiki::If::OP_defined::evaluate at line 38 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/If/OP_defined.pm, avg 24µs/call # 2 times (73µs+1.52ms) by Foswiki::Users::TopicUserMapping::eachGroupMember at line 665 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm, avg 796µs/call # once (37µs+171µs) by Foswiki::Store::Interfaces::QueryAlgorithm::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm:228] at line 217 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/Interfaces/QueryAlgorithm.pm
sub getPreference {
651145295µs my ( $this, $key ) = @_;
652
653145223µs unless ( $this->{_web} || $this->{_topic} ) {
654 return $this->{_session}->{prefs}->getPreference($key);
655 }
656
657 # make sure the preferences are parsed and cached
658145310µs unless ( $this->{_preferences} ) {
65969659µs6960.8s $this->{_preferences} =
# spent 60.8s making 69 calls to Foswiki::Prefs::loadPreferences, avg 881ms/call
660 $this->{_session}->{prefs}->loadPreferences($this);
661 }
6621451.44ms1453.97ms return $this->{_preferences}->get($key);
# spent 3.41ms making 67 calls to Foswiki::Prefs::Web::get, avg 51µs/call # spent 558µs making 78 calls to Foswiki::Prefs::TopicRAM::get, avg 7µs/call
663}
664
665=begin TML
666
667---++ ObjectMethod getContainer() -> $containerObject
668
669Get the container of this object; for example, the web that a topic is within
670
671=cut
672
673
# spent 2.63ms (598µs+2.04) within Foswiki::Meta::getContainer which was called 33 times, avg 80µs/call: # 33 times (598µs+2.04ms) by Foswiki::Access::TopicACLAccess::haveAccess at line 122 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Access/TopicACLAccess.pm, avg 80µs/call
sub getContainer {
6743357µs my $this = shift;
675
6763362µs if ( $this->{_topic} ) {
67733586µs332.04ms return Foswiki::Meta->new( $this->{_session}, $this->{_web} );
# spent 2.04ms making 33 calls to Foswiki::Meta::new, avg 62µs/call
678 }
679 if ( $this->{_web} ) {
680 return Foswiki::Meta->new( $this->{_session} );
681 }
682 ASSERT( 0, 'no container for this object type' ) if DEBUG;
683 return;
684}
685
686=begin TML
687
688---++ ObjectMethod existsInStore() -> $boolean
689
690A Meta object can be created for a web or topic that doesn't exist in the
691actual store (e.g. is in the process of being created). This method returns
692true if the corresponding web or topic really exists in the store.
693
694=cut
695
696
# spent 15.5ms (2.28+13.2) within Foswiki::Meta::existsInStore which was called 97 times, avg 160µs/call: # 55 times (1.23ms+7.34ms) by Foswiki::Meta::load at line 458, avg 156µs/call # 42 times (1.05ms+5.90ms) by Foswiki::Prefs::TopicRAM::new at line 31 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs/TopicRAM.pm, avg 166µs/call
sub existsInStore {
69797155µs my $this = shift;
69897198µs if ( defined $this->{_topic} ) {
699
700 # only checking for a topic existence already establishes a dependency
70197501µs97618µs $this->addDependency();
# spent 618µs making 97 calls to Foswiki::Meta::addDependency, avg 6µs/call
702
703971.24ms9712.6ms return $this->{_session}->{store}
# spent 12.6ms making 97 calls to Foswiki::Store::VC::Store::topicExists, avg 130µs/call
704 ->topicExists( $this->{_web}, $this->{_topic} );
705 }
706 elsif ( defined $this->{_web} ) {
707 return $this->{_session}->{store}->webExists( $this->{_web} );
708 }
709 else {
710 return 1; # the root always exists
711 }
712}
713
714=begin TML
715
716---++ ObjectMethod stringify( $debug ) -> $string
717
718Return a string version of the meta object. $debug adds
719extra debug info.
720
721=cut
722
723sub stringify {
724 my ( $this, $debug ) = @_;
725 my $s = $this->{_web};
726 if ( $this->{_topic} ) {
727 $s .= ".$this->{_topic} ";
728 $s .=
729 ( defined $this->{_loadedRev} )
730 ? $this->{_loadedRev}
731 : '(not loaded)'
732 if $debug;
733 $s .= "\n" . $this->getEmbeddedStoreForm();
734 }
735 return $s;
736}
737
738=begin TML
739
740---++ ObjectMethod addDependency() -> $this
741
742This establishes a caching dependency between $this and the
743base topic this session is currently rendering. The dependency
744will be asserted during Foswiki::PageCache::cachePage().
745See Foswiki::PageCache::addDependency().
746
747=cut
748
749
# spent 1.22ms within Foswiki::Meta::addDependency which was called 167 times, avg 7µs/call: # 97 times (618µs+0s) by Foswiki::Meta::existsInStore at line 701, avg 6µs/call # 70 times (600µs+0s) by Foswiki::Meta::loadVersion at line 1059, avg 9µs/call
sub addDependency {
750167442µs my $cache = $_[0]->{_session}->{cache};
7511671.05ms return unless $cache;
752 return $cache->addDependency( $_[0]->{_web}, $_[0]->{_topic} );
753}
754
755=begin TML
756
757---++ ObjectMethod fireDependency() -> $this
758
759Invalidates the cache bucket of the current meta object
760within the Foswiki::PageCache. See Foswiki::PageCache::fireDependency().
761
762=cut
763
764sub fireDependency {
765 my $cache = $_[0]->{_session}->{cache};
766 return unless $cache;
767 return $cache->fireDependency( $_[0]->{_web}, $_[0]->{_topic} );
768}
769
770############# WEB METHODS #############
771
772=begin TML
773
774---++ ObjectMethod populateNewWeb( [$baseWeb [, $opts]] )
775
776$baseWeb is the name of an existing web (a template web). If the
777base web is a system web, all topics in it
778will be copied into this web. If it is a normal web, only topics starting
779with 'Web' will be copied. If no base web is specified, an empty web
780(with no topics) will be created. If it is specified but does not exist,
781an error will be thrown.
782
783$opts is a ref to a hash that contains settings to be modified in
784the web preferences topic in the new web.
785
786=cut
787
788# SMELL: there seems to be no reason to call this method 'NewWeb', it can
789# be used to copy into an existing web and it does not appear to be
790# unexpectedly destructive.
791# perhaps refactor into something that takes a resultset as an input list?
792# (users have asked to be able to copy a SEARCH'd set of topics..)
793sub populateNewWeb {
794 my ( $this, $templateWeb, $opts ) = @_;
795 ASSERT( $this->{_web} && !$this->{_topic}, 'this is not a web object' )
796 if DEBUG;
797
798 my $session = $this->{_session};
799
800 my ( $parent, $new ) = $this->{_web} =~ m/^(.*)\/([^\.\/]+)$/;
801
802 if ($parent) {
803 unless ( $Foswiki::cfg{EnableHierarchicalWebs} ) {
804 throw Error::Simple( 'Unable to create '
805 . $this->{_web}
806 . ' - Hierarchical webs are disabled' );
807 }
808
809 unless ( $session->webExists($parent) ) {
810 throw Error::Simple( 'Parent web ' . $parent . ' does not exist' );
811 }
812 }
813
814 # Validate that template web exists, or error should be thrown
815 if ($templateWeb) {
816 unless ( $session->webExists($templateWeb) ) {
817 throw Error::Simple(
818 'Template web ' . $templateWeb . ' does not exist' );
819 }
820 }
821
822 # Make sure there is a preferences topic; this is how we know it's a web
823 my $prefsTopicObject;
824 if (
825 !$session->topicExists(
826 $this->{_web}, $Foswiki::cfg{WebPrefsTopicName}
827 )
828 )
829 {
830 my $prefsText = 'Preferences';
831 $prefsTopicObject =
832 $this->new( $this->{_session}, $this->{_web},
833 $Foswiki::cfg{WebPrefsTopicName}, $prefsText );
834 $prefsTopicObject->save();
835 }
836
837 if ($templateWeb) {
838 my $tWebObject = $this->new( $session, $templateWeb );
839 require Foswiki::WebFilter;
840 my $sys =
841 Foswiki::WebFilter->new('template')->ok( $session, $templateWeb );
842 my $it = $tWebObject->eachTopic();
843 while ( $it->hasNext() ) {
844 my $topic = $it->next();
845 next unless ( $sys || $topic =~ /^Web/ );
846 my $to =
847 Foswiki::Meta->load( $this->{_session}, $templateWeb, $topic );
848 $to->saveAs( $this->{_web}, $topic, ( forcenewrevision => 1 ) );
849 }
850 }
851
852 # patch WebPreferences in new web. We ignore permissions, because
853 # we are creating a new web here.
854 if ($opts) {
855 my $prefsTopicObject =
856 Foswiki::Meta->load( $this->{_session}, $this->{_web},
857 $Foswiki::cfg{WebPrefsTopicName} );
858 my $text = $prefsTopicObject->text;
859 foreach my $key ( keys %$opts ) {
860
861 #don't create the required params to create web.
862 next if ( $key eq 'BASEWEB' );
863 next if ( $key eq 'NEWWEB' );
864 next if ( $key eq 'NEWTOPIC' );
865 next if ( $key eq 'ACTION' );
866
867 if ( defined( $opts->{$key} ) ) {
868 if ( $text =~
869s/($Foswiki::regex{setRegex}$key\s*=).*?$/$1 $opts->{$key}/gm
870 )
871 {
872 }
873 else {
874
875 #this setting wasn't found, so we need to append it.
876 $text .= "\n * Web Created with KEY set\n";
877 $text .= "\n * Set $key = $opts->{$key}\n";
878 }
879 }
880 }
881 $prefsTopicObject->text($text);
882 $prefsTopicObject->save();
883 }
884}
885
886=begin TML
887
888---++ StaticMethod query($query, $inputTopicSet, \%options) -> $outputTopicSet
889
890Search for topic information
891=$query= must be a =Foswiki::*::Node= object.
892
893 * $inputTopicSet is a reference to an iterator containing a list
894 of topic in this web, if set to undef, the search/query algo will
895 create a new iterator using eachTopic()
896 and the web, topic and excludetopics options (as per SEARCH)
897 * web option - The web/s to search in - string can have the same form
898 as the =web= param of SEARCH
899
900
901Returns an Foswiki::Search::InfoCache iterator
902
903=cut
904
905
# spent 1.66s (46µs+1.66) within Foswiki::Meta::query which was called: # once (46µs+1.66s) by Foswiki::Search::searchWeb at line 364 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Search.pm
sub query {
90613µs my ( $query, $inputTopicSet, $options ) = @_;
907148µs11.66s return $Foswiki::Plugins::SESSION->{store}
# spent 1.66s making 1 call to Foswiki::Store::VC::Store::query
908 ->query( $query, $inputTopicSet, $Foswiki::Plugins::SESSION, $options );
909}
910
911=begin TML
912
913---++ ObjectMethod eachWeb( $all ) -> $iterator
914
915Return an iterator over each subweb. If =$all= is set, will return a
916list of all web names *under* the current location. Returns web pathnames
917relative to $this.
918
919Only valid on webs and the root.
920
921=cut
922
923sub eachWeb {
924 my ( $this, $all ) = @_;
925
926 # Works on the root, so {_web} may be undef
927 ASSERT( !$this->{_topic}, 'this object may not contain webs' ) if DEBUG;
928 return $this->{_session}->{store}->eachWeb( $this, $all );
929
930}
931
932=begin TML
933
934---++ ObjectMethod eachTopic() -> $iterator
935
936Return an iterator over each topic name in the web. Only valid on webs.
937
938=cut
939
940
# spent 170ms (54µs+170) within Foswiki::Meta::eachTopic which was called: # once (54µs+170ms) by Foswiki::Search::InfoCache::getTopicListIterator at line 456 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Search/InfoCache.pm
sub eachTopic {
94112µs my ($this) = @_;
94215µs13µs ASSERT( $this->{_web} && !$this->{_topic}, 'this is not a web object' )
# spent 3µs making 1 call to Assert::ASSERTS_OFF
943 if DEBUG;
94412µs if ( !$this->{_web} ) {
945
946 # Root
947 require Foswiki::ListIterator;
948 return new Foswiki::ListIterator( [] );
949 }
950143µs1170ms return $this->{_session}->{store}->eachTopic($this);
# spent 170ms making 1 call to Foswiki::Store::VC::Store::eachTopic
951}
952
953=begin TML
954
955---++ ObjectMethod eachAttachment() -> $iterator
956
957Return an iterator over each attachment name in the topic.
958Only valid on topics.
959
960The list of the names of attachments stored for the given topic may be a
961longer list than the list that comes from the topic meta-data, which may
962only lists the attachments that are normally visible to the user.
963
964=cut
965
966sub eachAttachment {
967 my ($this) = @_;
968 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
969 if DEBUG;
970 return $this->{_session}->{store}->eachAttachment($this);
971}
972
973=begin TML
974
975---++ ObjectMethod eachChange( $time ) -> $iterator
976
977Get an iterator over the list of all the changes in the web between
978=$time= and now. $time is a time in seconds since 1st Jan 1970, and is not
979guaranteed to return any changes that occurred before (now -
980{Store}{RememberChangesFor}). Changes are returned in most-recent-first
981order.
982
983Only valid for a web.
984
985=cut
986
987sub eachChange {
988 my ( $this, $time ) = @_;
989
990 # not valid at root level
991 ASSERT( $this->{_web} && !$this->{_topic}, 'this is not a web object' )
992 if DEBUG;
993 return $this->{_session}->{store}->eachChange( $this, $time );
994}
995
996############# TOPIC METHODS #############
997
998=begin TML
999
1000---++ ObjectMethod loadVersion($rev) -> $version
1001
1002Load the object from the store. The object must not be already loaded
1003with a different rev (verified by an ASSERT)
1004
1005See =getLoadedRev= to determine what revision is currently being viewed.
1006 * =$rev= - revision to load. If undef, 0, '' or > max available rev, will
1007 load the latest rev. If the revision is in range but does not exist,
1008 then will return an unloaded meta object (getLoadedRev() will be undef)
1009
1010Returns the version identifier for the loaded revision. (and undef if it failed to load)
1011
1012WARNING: see notes on revision numbers under =getLoadedRev=
1013
1014=cut
1015
1016
# spent 93.8s (4.45ms+93.8) within Foswiki::Meta::loadVersion which was called 70 times, avg 1.34s/call: # 56 times (3.61ms+32.8s) by Foswiki::Meta::load at line 477, avg 585ms/call # 7 times (425µs+61.0s) by Foswiki::Meta::text at line 1092, avg 8.71s/call # 7 times (415µs+34.3ms) by Foswiki::Access::TopicACLAccess::_getACL at line 200 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Access/TopicACLAccess.pm, avg 4.95ms/call
sub loadVersion {
101770143µs my ( $this, $rev ) = @_;
1018
101970122µs return unless $this->{_topic};
1020
1021 # If no specific rev was requested, check that the latest rev is
1022 # loaded.
102370223µs if ( !defined $rev || !$rev ) {
1024
1025 # Trying to load the latest
102670178µs if ( $this->{_latestIsLoaded} ) {
1027
1028 #TODO: these asserts trip up Comment Plugin
1029 #ASSERT(defined($this->{_loadedRev})) if DEBUG;
1030 #ASSERT($rev == $this->{_loadedRev}) if DEBUG;
1031 return;
1032 }
103370328µs70224µs ASSERT( not( $this->{_loadedRev} ) ) if DEBUG;
# spent 224µs making 70 calls to Assert::ASSERTS_OFF, avg 3µs/call
1034 }
1035 elsif ( defined( $this->{_loadedRev} ) ) {
1036
1037 # Cannot load a different rev into an already-loaded
1038 # Foswiki::Meta object
1039 $rev = -1 unless defined $rev;
1040 ASSERT( 0, "Attempt to reload $rev over version $this->{_loadedRev}" )
1041 if DEBUG;
1042 }
1043
1044 # Is it already loaded?
104570322µs70217µs ASSERT( !($rev) or $rev =~ /^\s*\d+\s*/ ) if DEBUG; # looks like a number
# spent 217µs making 70 calls to Assert::ASSERTS_OFF, avg 3µs/call
104670112µs return $this->{_loadedRev}
1047 if ( $rev && $this->{_loadedRev} && $rev == $this->{_loadedRev} );
1048
104970324µs70213µs ASSERT( not( $this->{_loadedRev} ) ) if DEBUG;
# spent 213µs making 70 calls to Assert::ASSERTS_OFF, avg 3µs/call
1050
105170853µs7093.8s ( $this->{_loadedRev}, $this->{_latestIsLoaded} ) =
# spent 93.8s making 70 calls to Foswiki::Store::VC::Store::readTopic, avg 1.34s/call
1052 $this->{_session}->{store}->readTopic( $this, $rev );
105370210µs if ( defined( $this->{_loadedRev} ) ) {
1054
1055 # Make sure text always has a value once loadVersion has been called
1056 # once.
105770137µs $this->{_text} = '' unless defined $this->{_text};
1058
105970531µs70600µs $this->addDependency();
# spent 600µs making 70 calls to Foswiki::Meta::addDependency, avg 9µs/call
1060 }
1061 else {
1062
1063 #we didn't load, so how could it be latest?
1064 ASSERT( not $this->{_latestIsLoaded} ) if DEBUG;
1065 }
1066
106770651µs return $this->{_loadedRev};
1068}
1069
1070=begin TML
1071
1072---++ ObjectMethod text([$text]) -> $text
1073
1074Get/set the topic body text. If $text is undef, gets the value, if it is
1075defined, sets the value to that and returns the new text.
1076
1077Be warned - it can return undef - when a topic exists but has no topicText.
1078
1079=cut
1080
1081
# spent 61.0s (2.21ms+61.0) within Foswiki::Meta::text which was called 91 times, avg 670ms/call: # 42 times (1.02ms+61.0s) by Foswiki::Prefs::Parser::parse at line 39 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs/Parser.pm, avg 1.45s/call # 22 times (417µs+69µs) by Foswiki::Search::formatResults at line 750 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Search.pm, avg 22µs/call # 20 times (409µs+71µs) by Foswiki::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm:326] at line 231 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm, avg 24µs/call # 3 times (93µs+11µs) by Foswiki::Templates::_readTemplateFile at line 519 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Templates.pm, avg 35µs/call # 2 times (202µs+6µs) by Foswiki::Func::readTopic at line 1533 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm, avg 104µs/call # once (36µs+4µs) by Foswiki::UI::View::view at line 175 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm # once (25µs+3µs) by Foswiki::Form::_parseFormDefinition at line 208 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Form.pm
sub text {
108291174µs my ( $this, $val ) = @_;
108391412µs91306µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 306µs making 91 calls to Assert::ASSERTS_OFF, avg 3µs/call
1084 if DEBUG;
108591240µs if ( defined($val) ) {
1086 $this->{_text} = $val;
1087 }
1088 else {
1089
1090 # Lazy load. Reload with no params will reload the _loadedRev,
1091 # or load the latest if that is not defined.
109291232µs761.0s $this->loadVersion() unless defined( $this->{_text} );
# spent 61.0s making 7 calls to Foswiki::Meta::loadVersion, avg 8.71s/call
1093 }
1094911.10ms return $this->{_text};
1095}
1096
1097=begin TML
1098
1099---++ ObjectMethod put($type, \%args)
1100
1101Put a hash of key=value pairs into the given type set in this meta. This
1102will *not* replace another value with the same name (for that see =putKeyed=)
1103
1104For example,
1105<verbatim>
1106$meta->put( 'FIELD', { name => 'MaxAge', title => 'Max Age', value =>'103' } );
1107</verbatim>
1108
1109=cut
1110
1111
# spent 3.94ms (3.18+756µs) within Foswiki::Meta::put which was called 70 times, avg 56µs/call: # 70 times (3.18ms+756µs) by Foswiki::Meta::_readMETA at line 3689, avg 56µs/call
sub put {
111270168µs my ( $this, $type, $args ) = @_;
111370322µs70240µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 240µs making 70 calls to Assert::ASSERTS_OFF, avg 3µs/call
1114 if DEBUG;
111570308µs70224µs ASSERT( defined $type ) if DEBUG;
# spent 224µs making 70 calls to Assert::ASSERTS_OFF, avg 3µs/call
111670386µs70292µs ASSERT( defined $args && ref($args) eq 'HASH' ) if DEBUG;
# spent 292µs making 70 calls to Assert::ASSERTS_OFF, avg 4µs/call
1117
111870234µs unless ( $this->{$type} ) {
111970238µs $this->{$type} = [];
112070207µs $this->{_indices}->{$type} = {};
1121 }
1122
112370122µs my $data = $this->{$type};
11247099µs my $i = 0;
112570187µs if ($data) {
1126
1127 # overwrite old single value
112870115µs if ( scalar(@$data) && defined $data->[0]->{name} ) {
1129 delete $this->{_indices}->{$type}->{ $data->[0]->{name} };
1130 }
113170167µs $data->[0] = $args;
1132 }
1133 else {
1134 $i = push( @$data, $args ) - 1;
1135 }
113670473µs if ( defined $args->{name} ) {
1137 $this->{_indices}->{$type} ||= {};
1138 $this->{_indices}->{$type}->{ $args->{name} } = $i;
1139 }
1140}
1141
1142=begin TML
1143
1144---++ ObjectMethod putKeyed($type, \%args)
1145
1146Put a hash of key=value pairs into the given type set in this meta, replacing
1147any existing value with the same key.
1148
1149For example,
1150<verbatim>
1151$meta->putKeyed( 'FIELD',
1152 { name => 'MaxAge', title => 'Max Age', value =>'103' } );
1153</verbatim>
1154
1155=cut
1156
1157# Note: Array is used instead of a hash to preserve sequence
1158
1159
# spent 19.9ms (15.6+4.35) within Foswiki::Meta::putKeyed which was called 339 times, avg 59µs/call: # 339 times (15.6ms+4.35ms) by Foswiki::Meta::_readMETA at line 3686, avg 59µs/call
sub putKeyed {
1160339684µs my ( $this, $type, $args ) = @_;
11613391.54ms3391.19ms ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 1.19ms making 339 calls to Assert::ASSERTS_OFF, avg 4µs/call
1162 if DEBUG;
11633391.45ms3391.05ms ASSERT($type) if DEBUG;
# spent 1.05ms making 339 calls to Assert::ASSERTS_OFF, avg 3µs/call
11643391.46ms3391.04ms ASSERT( $args && ref($args) eq 'HASH' ) if DEBUG;
# spent 1.04ms making 339 calls to Assert::ASSERTS_OFF, avg 3µs/call
1165339643µs my $keyName = $args->{name};
11663391.48ms3391.06ms ASSERT( $keyName, join( ',', keys %$args ) ) if DEBUG;
# spent 1.06ms making 339 calls to Assert::ASSERTS_OFF, avg 3µs/call
1167
1168339697µs unless ( $this->{$type} ) {
1169102334µs $this->{$type} = [];
1170102221µs $this->{_indices}->{$type} = {};
1171 }
1172
1173339534µs my $data = $this->{$type};
1174
1175 # The \% shouldn't be necessary, but it is
11766781.63ms my $indices = \%{ $this->{_indices}->{$type} };
11773392.32ms if ( defined $indices->{$keyName} ) {
1178 $data->[ $indices->{$keyName} ] = $args;
1179 }
1180 else {
11813391.15ms $indices->{$keyName} = push( @$data, $args ) - 1;
1182 }
1183}
1184
1185=begin TML
1186
1187---++ ObjectMethod putAll
1188
1189Replaces all the items of a given key with a new array.
1190
1191For example,
1192<verbatim>
1193$meta->putAll( 'FIELD',
1194 { name => 'MinAge', title => 'Min Age', value =>'50' },
1195 { name => 'MaxAge', title => 'Max Age', value =>'103' },
1196 { name => 'HairColour', title => 'Hair Colour', value =>'white' }
1197 );
1198</verbatim>
1199
1200=cut
1201
1202sub putAll {
1203 my ( $this, $type, @array ) = @_;
1204 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
1205 if DEBUG;
1206
1207 my %indices;
1208 for ( my $i = 0 ; $i < scalar(@array) ; $i++ ) {
1209 if ( defined $array[$i]->{name} ) {
1210 $indices{ $array[$i]->{name} } = $i;
1211 }
1212 }
1213 $this->{$type} = \@array;
1214 $this->{_indices}->{$type} = \%indices;
1215}
1216
1217=begin TML
1218
1219---++ ObjectMethod get( $type, $key ) -> \%hash
1220
1221Find the value of a meta-datum in the map. If the type is
1222keyed (identified by a =name=), the =$key= parameter is required
1223to say _which_ entry you want. Otherwise you will just get the first value.
1224
1225If you want all the keys of a given type use the 'find' method.
1226
1227The result is a reference to the hash for the item.
1228
1229For example,
1230<verbatim>
1231my $ma = $meta->get( 'FIELD', 'MinAge' );
1232my $topicinfo = $meta->get( 'TOPICINFO' ); # get the TOPICINFO hash
1233</verbatim>
1234
1235=cut
1236
1237
# spent 6.29ms (5.22+1.07) within Foswiki::Meta::get which was called 264 times, avg 24µs/call: # 70 times (1.54ms+329µs) by Foswiki::Store::VC::Store::readTopic at line 126 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Store.pm, avg 27µs/call # 70 times (1.30ms+225µs) by Foswiki::Meta::setEmbeddedStoreForm at line 3528, avg 22µs/call # 42 times (692µs+139µs) by Foswiki::Prefs::Parser::parse at line 78 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs/Parser.pm, avg 20µs/call # 29 times (479µs+88µs) by Foswiki::Meta::getRevisionInfo at line 1490, avg 20µs/call # 28 times (781µs+207µs) by Foswiki::Form::renderForDisplay at line 603 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Form.pm, avg 35µs/call # 21 times (356µs+73µs) by Foswiki::Store::VC::Store::readTopic at line 115 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Store.pm, avg 20µs/call # 2 times (36µs+6µs) by Foswiki::Serialise::Embedded::getEmbeddedStoreForm at line 79 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Serialise/Embedded.pm, avg 21µs/call # once (22µs+3µs) by Foswiki::Meta::getFormName at line 1707 # once (18µs+4µs) by Foswiki::Render::renderParent at line 156 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm
sub get {
1238264633µs my ( $this, $type, $name ) = @_;
12392641.25ms264980µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 980µs making 264 calls to Assert::ASSERTS_OFF, avg 4µs/call
1240 if DEBUG;
1241
1242264497µs my $data = $this->{$type};
1243264358µs if ($data) {
1244221311µs if ( defined $name ) {
1245
1246#this code presumes the _indices are there (Sven would like it to re-create when needed..)
1247# Paul.H notes that we trip over this one when meta obj is unloaded (I think, see Item10927)
124828126µs2893µs ASSERT( defined( $this->{_indices} ) ) if DEBUG;
# spent 93µs making 28 calls to Assert::ASSERTS_OFF, avg 3µs/call
12492854µs my $indices = $this->{_indices}->{$type};
12502836µs return undef unless defined $indices;
125128107µs return undef unless defined $indices->{$name};
12521485µs return $data->[ $indices->{$name} ];
1253 }
1254 else {
12551931.30ms return $data->[0];
1256 }
1257 }
1258
125943252µs return undef;
1260}
1261
1262=begin TML
1263
1264---++ ObjectMethod find ( $type ) -> @values
1265
1266Get all meta data for a specific type.
1267Returns the array stored for the type. This will be zero length
1268if there are no entries.
1269
1270For example,
1271<verbatim>
1272my $attachments = $meta->find( 'FILEATTACHMENT' );
1273</verbatim>
1274
1275=cut
1276
1277
# spent 1.55ms (1.34+208µs) within Foswiki::Meta::find which was called 60 times, avg 26µs/call: # 42 times (947µs+149µs) by Foswiki::Prefs::Parser::parse at line 68 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs/Parser.pm, avg 26µs/call # 10 times (213µs+31µs) by Foswiki::Prefs::Parser::parse at line 80 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Prefs/Parser.pm, avg 24µs/call # 7 times (160µs+24µs) by Foswiki::Render::renderFORMFIELD at line 1047 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm, avg 26µs/call # once (24µs+4µs) by Foswiki::Attach::renderMetaData at line 85 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Attach.pm
sub find {
127860126µs my ( $this, $type ) = @_;
127960281µs60208µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 208µs making 60 calls to Assert::ASSERTS_OFF, avg 3µs/call
1280 if DEBUG;
1281
128260118µs my $itemsr = $this->{$type};
128360123µs my @items = ();
1284
128560111µs if ($itemsr) {
12863083µs @items = @$itemsr;
1287 }
1288
128960373µs return @items;
1290}
1291
1292=begin TML
1293
1294---++ ObjectMethod remove($type, $key)
1295
1296With no type, will remove all the meta-data in the object.
1297
1298With a $type but no $key, will remove _all_ items of that type
1299(so for example if $type were FILEATTACHMENT it would remove all of them)
1300
1301With a $type and a $key it will remove only the specific item.
1302
1303=cut
1304
1305sub remove {
1306 my ( $this, $type, $name ) = @_;
1307 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
1308 if DEBUG;
1309
1310 if ($type) {
1311 my $data = $this->{$type};
1312 return unless defined $data;
1313 if ($name) {
1314 my $indices = $this->{_indices}->{$type};
1315 if ( defined $indices ) {
1316 my $i = $indices->{$name};
1317 return unless defined $i;
1318 splice( @$data, $i, 1 );
1319 delete $indices->{$name};
1320 for ( my $i = 0 ; $i < scalar(@$data) ; $i++ ) {
1321 my $item = $data->[$i];
1322 next unless exists $item->{name};
1323 $indices->{ $item->{name} } = $i;
1324 }
1325 }
1326 }
1327 else {
1328 delete $this->{$type};
1329 delete $this->{_indices}->{$type};
1330 }
1331 }
1332 else {
1333 foreach my $entry ( keys %$this ) {
1334 unless ( $entry =~ /^_/ ) {
1335 delete $this->{$entry};
1336 }
1337 }
1338 $this->{_indices} = {};
1339 }
1340}
1341
1342=begin TML
1343
1344---++ ObjectMethod copyFrom( $otherMeta [, $type [, $nameFilter]] )
1345
1346Copy all entries of a type from another meta data set. This
1347will destroy the old values for that type, unless the
1348copied object doesn't contain entries for that type, in which
1349case it will retain the old values.
1350
1351If $type is undef, will copy ALL TYPES.
1352
1353If $nameFilter is defined (a perl regular expression), it will copy
1354only data where ={name}= matches $nameFilter.
1355
1356Does *not* copy web, topic or text.
1357
1358=cut
1359
1360sub copyFrom {
1361 my ( $this, $other, $type, $filter ) = @_;
1362 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
1363 if DEBUG;
1364 ASSERT( $other->isa('Foswiki::Meta') && $other->{_web} && $other->{_topic},
1365 'other is not a topic object' )
1366 if DEBUG;
1367
1368 if ($type) {
1369 return if $type =~ /^_/;
1370 my @data;
1371 foreach my $item ( @{ $other->{$type} } ) {
1372 if ( !$filter
1373 || ( $item->{name} && $item->{name} =~ /$filter/ ) )
1374 {
1375 ASSERT( defined($item) ) if DEBUG;
1376 my %datum = %$item;
1377 push( @data, \%datum );
1378 }
1379 }
1380 $this->putAll( $type, @data );
1381 }
1382 else {
1383 foreach my $k ( keys %$other ) {
1384 unless ( $k =~ /^_/ ) {
1385 $this->copyFrom( $other, $k );
1386 }
1387 }
1388 }
1389}
1390
1391=begin TML
1392
1393---++ ObjectMethod count($type) -> $integer
1394
1395Return the number of entries of the given type
1396
1397=cut
1398
1399
# spent 1.42ms (1.19+231µs) within Foswiki::Meta::count which was called 70 times, avg 20µs/call: # 70 times (1.19ms+231µs) by Foswiki::Meta::setEmbeddedStoreForm at line 3579, avg 20µs/call
sub count {
140070160µs my ( $this, $type ) = @_;
140170323µs70231µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 231µs making 70 calls to Assert::ASSERTS_OFF, avg 3µs/call
1402 if DEBUG;
140370138µs my $data = $this->{$type};
1404
140570430µs return scalar @$data if ( defined($data) );
1406
1407318µs return 0;
1408}
1409
1410=begin TML
1411
1412---++ ObjectMethod setRevisionInfo( %opts )
1413
1414Set TOPICINFO information on the object, as specified by the parameters.
1415 * =version= - the revision number
1416 * =time= - the time stamp
1417 * =author= - the user id (cUID)
1418 * + additional data fields to save e.g. reprev, comment
1419
1420=cut
1421
1422sub setRevisionInfo {
1423 my ( $this, %data ) = @_;
1424 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
1425 if DEBUG;
1426
1427 my $ti = $this->get('TOPICINFO') || {};
1428
1429 foreach my $k ( keys %data ) {
1430 $ti->{$k} = $data{$k};
1431 }
1432
1433 # compatibility; older versions of the code use
1434 # RCS rev numbers. Save with them so old code can
1435 # read these topics
1436 ASSERT( defined $ti->{version} ) if DEBUG;
1437 $ti->{version} = 1 if $ti->{version} < 1;
1438 $ti->{version} = $ti->{version};
1439 $ti->{format} = $EMBEDDING_FORMAT_VERSION;
1440
1441 $this->put( 'TOPICINFO', $ti );
1442}
1443
1444=begin TML
1445
1446---++ ObjectMethod getRevisionInfo() -> \%info
1447
1448Return revision info for the loaded revision in %info with at least:
1449 * ={date}= in epochSec
1450 * ={author}= canonical user ID
1451 * ={version}= the revision number
1452
1453---++ ObjectMethod getRevisionInfo() -> ( $revDate, $author, $rev, $comment )
1454
1455Limited backwards compatibility for plugins that assume the 1.0.x interface
1456The comment is *always* blank
1457
1458=cut
1459
1460
# spent 1.63ms (973µs+657µs) within Foswiki::Meta::getRevisionInfo which was called 29 times, avg 56µs/call: # 23 times (766µs+515µs) by Foswiki::MetaCache::get at line 219 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/MetaCache.pm, avg 56µs/call # 6 times (208µs+142µs) by Foswiki::Render::renderRevisionInfo at line 1729 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm, avg 58µs/call
sub getRevisionInfo {
14612948µs my $this = shift;
146229127µs2991µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 91µs making 29 calls to Assert::ASSERTS_OFF, avg 3µs/call
1463 if DEBUG;
1464
14652937µs my $info;
14662953µs if ( not defined( $this->{_loadedRev} )
1467 and not Foswiki::Func::topicExists( $this->{_web}, $this->{_topic} ) )
1468 {
1469
1470#print STDERR "topic does not exist - at least, _loadedRev is not set..(".$this->{_web} .' '. $this->{_topic}.")\n";
1471#this does not exist on disk - no reason to goto the store for the defaults
1472#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
1473 $info = {
1474 date => 0,
1475 author => $Foswiki::Users::BaseUserMapping::DEFAULT_USER_CUID,
1476 version => 0,
1477 format => $EMBEDDING_FORMAT_VERSION,
1478 };
1479 return $info;
1480 }
1481
1482 # This used to try and get revision info from the meta
1483 # information and only kick down to the Store module for the
1484 # same information if it was not present. However there have
1485 # been several cases where the meta information in the cache
1486 # is badly out of step with the store, and the conclusion is
1487 # that it can't be trusted. For this reason, when meta is read
1488 # TOPICINFO version field is automatically undefined, which
1489 # forces this function to re-get it from the store.
149029160µs29566µs my $topicinfo = $this->get('TOPICINFO');
# spent 566µs making 29 calls to Foswiki::Meta::get, avg 20µs/call
1491
14922995µs if ( $topicinfo && defined $topicinfo->{version} ) {
149329139µs $info = {
1494 date => $topicinfo->{date},
1495 author => $topicinfo->{author},
1496 version => $topicinfo->{version},
1497 };
1498 }
1499 else {
1500
1501 # Delegate to the store
1502 $info = $this->{_session}->{store}->getVersionInfo($this);
1503
1504 # cache the result
1505 $this->setRevisionInfo(%$info);
1506 }
1507
15082944µs if (wantarray) {
1509
1510 # Backwards compatibility for 1.0.x plugins
1511 return ( $info->{date}, $info->{author}, $info->{version}, '' );
1512 }
1513 else {
151429180µs return $info;
1515 }
1516}
1517
1518# Determines, and caches, the topic revision info of the base version,
1519# SMELL: this is a horrid little legacy of the InfoCache object, and
1520# should be done away with.
1521sub getRev1Info {
1522 my ( $this, $attr ) = @_;
1523 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
1524 if DEBUG;
1525
1526#my ( $web, $topic ) = Foswiki::Func::normalizeWebTopicName( $this->{_defaultWeb}, $webtopic );
1527 my $web = $this->web;
1528 my $topic = $this->topic;
1529
1530 if ( !defined( $this->{_getRev1Info} ) ) {
1531 $this->{_getRev1Info} = {};
1532 }
1533 my $info = $this->{_getRev1Info};
1534 unless ( defined $info->{$attr} ) {
1535 my $ri = $info->{rev1info};
1536 unless ($ri) {
1537 my $tmp = Foswiki::Meta->load( $this->{_session}, $web, $topic, 1 );
1538 $info->{rev1info} = $ri = $tmp->getRevisionInfo();
1539 }
1540
1541 if ( $attr eq 'createusername' ) {
1542 $info->{createusername} =
1543 $this->{_session}->{users}->getLoginName( $ri->{author} );
1544 }
1545 elsif ( $attr eq 'createwikiname' ) {
1546 $info->{createwikiname} =
1547 $this->{_session}->{users}->getWikiName( $ri->{author} );
1548 }
1549 elsif ( $attr eq 'createwikiusername' ) {
1550 $info->{createwikiusername} =
1551 $this->{_session}->{users}->webDotWikiName( $ri->{author} );
1552 }
1553 elsif ($attr eq 'createdate'
1554 or $attr eq 'createlongdate'
1555 or $attr eq 'created' )
1556 {
1557 $info->{created} = $ri->{date};
1558
1559 # Don't pass Foswiki::Time an undef value
1560 if ( defined $ri->{date} ) {
1561 require Foswiki::Time;
1562 $info->{createdate} = Foswiki::Time::formatTime( $ri->{date} );
1563
1564 #TODO: wow thats disgusting.
1565 $info->{created} = $info->{createlongdate} =
1566 $info->{createdate};
1567 }
1568 }
1569 }
1570 return $info->{$attr};
1571}
1572
1573=begin TML
1574
1575---++ ObjectMethod merge( $otherMeta, $formDef )
1576
1577 * =$otherMeta= - a block of meta-data to merge with $this
1578 * =$formDef= reference to a Foswiki::Form that gives the types of the fields in $this
1579
1580Merge the data in the other meta block.
1581 * File attachments that only appear in one set are preserved.
1582 * Form fields that only appear in one set are preserved.
1583 * Form field values that are different in each set are text-merged
1584 * We don't merge for field attributes or title
1585 * Topic info is not touched
1586 * The =mergeable= 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.
1587
1588=cut
1589
1590sub merge {
1591 my ( $this, $other, $formDef ) = @_;
1592 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
1593 if DEBUG;
1594 ASSERT( $other->isa('Foswiki::Meta') && $other->{_web} && $other->{_topic},
1595 'other is not a topic object' )
1596 if DEBUG;
1597
1598 my $data = $other->{FIELD};
1599 if ($data) {
1600 foreach my $otherD (@$data) {
1601 my $thisD = $this->get( 'FIELD', $otherD->{name} );
1602 if ( $thisD && $thisD->{value} ne $otherD->{value} ) {
1603 if ( $formDef->isTextMergeable( $thisD->{name} ) ) {
1604 require Foswiki::Merge;
1605 my $merged = Foswiki::Merge::merge2(
1606 'A',
1607 $otherD->{value},
1608 'B',
1609 $thisD->{value},
1610 '.*?\s+',
1611 $this->{_session},
1612 $formDef->getField( $thisD->{name} )
1613 );
1614
1615 # SMELL: we don't merge attributes or title
1616 $thisD->{value} = $merged;
1617 }
1618 }
1619 elsif ( !$thisD ) {
1620 $this->putKeyed( 'FIELD', $otherD );
1621 }
1622 }
1623 }
1624
1625 $data = $other->{FILEATTACHMENT};
1626 if ($data) {
1627 foreach my $otherD (@$data) {
1628 my $thisD = $this->get( 'FILEATTACHMENT', $otherD->{name} );
1629 if ( !$thisD ) {
1630 $this->putKeyed( 'FILEATTACHMENT', $otherD );
1631 }
1632 }
1633 }
1634}
1635
1636=begin TML
1637
1638---++ ObjectMethod forEachSelectedValue( $types, $keys, \&fn, \%options )
1639
1640Iterate over the values selected by the regular expressions in $types and
1641$keys.
1642 * =$types= - regular expression matching the names of fields to be processed. Will default to qr/^[A-Z]+$/ if undef.
1643 * =$keys= - regular expression matching the names of keys to be processed. Will default to qr/^[a-z]+$/ if undef.
1644
1645Iterates over each value, calling =\&fn= on each, and replacing the value
1646with the result of \&fn.
1647
1648\%options will be passed on to $fn, with the following additions:
1649 * =_type= => the type name (e.g. "FILEATTACHMENT")
1650 * =_key= => the key name (e.g. "user")
1651
1652=cut
1653
1654sub forEachSelectedValue {
1655 my ( $this, $types, $keys, $fn, $options ) = @_;
1656 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
1657 if DEBUG;
1658
1659 $types ||= qr/^[A-Z]+$/;
1660 $keys ||= qr/^[a-z]+$/;
1661
1662 foreach my $type ( grep { /$types/ } keys %$this ) {
1663 $options->{_type} = $type;
1664 my $data = $this->{$type};
1665 next unless $data;
1666 foreach my $datum (@$data) {
1667 foreach my $key ( grep { /$keys/ } keys %$datum ) {
1668 $options->{_key} = $key;
1669 $datum->{$key} = &$fn( $datum->{$key}, $options );
1670 }
1671 }
1672 }
1673}
1674
1675=begin TML
1676
1677---++ ObjectMethod getParent() -> $parent
1678
1679Gets the TOPICPARENT name. Safe shortcut for =$meta->get('TOPICPARENT')->{name}
1680Returns the emty string if there is no parent.
1681
1682=cut
1683
1684sub getParent {
1685 my ($this) = @_;
1686
1687 my $value = '';
1688 my $parent = $this->get('TOPICPARENT');
1689 $value = $parent->{name} if ($parent);
1690
1691 # Return empty string (not undef), if TOPICPARENT meta is broken
1692 $value = '' if ( !defined $value );
1693 return $value;
1694}
1695
1696=begin TML
1697
1698---++ ObjectMethod getFormName() -> $formname
1699
1700Returns the name of the FORM, or '' if none.
1701
1702=cut
1703
1704
# spent 48µs (24+24) within Foswiki::Meta::getFormName which was called: # once (24µs+24µs) by Foswiki::Meta::renderFormForDisplay at line 1728
sub getFormName {
170512µs my ($this) = @_;
1706
170716µs124µs my $aForm = $this->get('FORM');
# spent 24µs making 1 call to Foswiki::Meta::get
170811µs if ($aForm) {
1709110µs return $aForm->{name};
1710 }
1711 return '';
1712}
1713
1714=begin TML
1715
1716---++ ObjectMethod renderFormForDisplay() -> $html
1717
1718Render the form contained in the meta for display.
1719
1720=cut
1721
1722# SMELL: this is part of the View and should be moved closer to the renderer
1723
# spent 1.36s (4.64ms+1.36) within Foswiki::Meta::renderFormForDisplay which was called: # once (4.64ms+1.36s) by Foswiki::META at line 22 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/META.pm
sub renderFormForDisplay {
172412µs my ($this) = @_;
172514µs13µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 3µs making 1 call to Assert::ASSERTS_OFF
1726 if DEBUG;
1727
172817µs148µs my $fname = $this->getFormName();
# spent 48µs making 1 call to Foswiki::Meta::getFormName
1729
17301166µs require Foswiki::Form;
173112µs require Foswiki::OopsException;
173211µs return '' unless $fname;
1733
173411µs my $form;
173511µs my $result;
1736
# spent 1.35s (38µs+1.35) within Foswiki::Meta::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Meta.pm:1739] which was called: # once (38µs+1.35s) by Error::subs::try at line 416 of Error.pm
try {
1737111µs11.34s $form = new Foswiki::Form( $this->{_session}, $this->{_web}, $fname );
# spent 1.34s making 1 call to Foswiki::Form::new
1738123µs110.4ms $result = $form->renderForDisplay($this);
# spent 10.4ms making 1 call to Foswiki::Form::renderForDisplay
1739 }
1740 catch Foswiki::OopsException with {
1741
1742 # Make pseudo-form from field data
1743 $form =
1744 new Foswiki::Form( $this->{_session}, $this->{_web}, $fname, $this );
1745 $result =
1746 $this->{_session}->inlineAlert( 'alerts', 'formdef_missing', $fname );
1747 $result .= $form->renderForDisplay($this) if $form;
1748160µs322µs };
# spent 16µs making 1 call to Error::catch # spent 6µs making 1 call to Error::subs::with # spent 1.35s making 1 call to Error::subs::try, recursion: max depth 1, sum of overlapping time 1.35s
1749
1750117µs return $result;
1751}
1752
1753=begin TML
1754
1755---++ ObjectMethod renderFormFieldForDisplay($name, $format, $attrs) -> $text
1756
1757Render a single formfield, using the $format. See
1758Foswiki::Form::FormField::renderForDisplay for a description of how the value
1759is rendered.
1760
1761=cut
1762
1763# SMELL: this is part of the View and should be moved closer to the renderer
1764sub renderFormFieldForDisplay {
1765 my ( $this, $name, $format, $attrs ) = @_;
1766 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
1767 if DEBUG;
1768
1769 my $value;
1770 my $mf = $this->get( 'FIELD', $name );
1771 unless ($mf) {
1772
1773 # Not a valid field name, maybe it's a title.
1774 require Foswiki::Form;
1775 $name = Foswiki::Form::fieldTitle2FieldName($name);
1776 $mf = $this->get( 'FIELD', $name );
1777 }
1778 return '' unless $mf; # field not found
1779
1780 $value = $mf->{value};
1781
1782 # remove nop exclamation marks from form field value before it is put
1783 # inside a format like [[$topic][$formfield()]] that prevents it being
1784 # detected
1785 $value =~ s/!(\w+)/<nop>$1/gos;
1786
1787 my $fname = $this->getFormName();
1788 if ($fname) {
1789 require Foswiki::Form;
1790 my $result;
1791 try {
1792 my $form =
1793 new Foswiki::Form( $this->{_session}, $this->{_web}, $fname );
1794 my $field = $form->getField($name);
1795 if ($field) {
1796 $result = $field->renderForDisplay( $format, $value, $attrs );
1797 }
1798 }
1799 catch Foswiki::OopsException with {
1800
1801 # Form not found, ignore
1802 };
1803
1804 return $result if defined $result;
1805 }
1806
1807 # Form or field wasn't found, do your best!
1808 my $f = $this->get( 'FIELD', $name );
1809 if ($f) {
1810 $format =~ s/\$title/$f->{title}/;
1811 require Foswiki::Render;
1812 $value = Foswiki::Render::protectFormFieldValue( $value, $attrs );
1813 $format =~ s/\$value/$value/;
1814 }
1815 return $format;
1816}
1817
1818=begin TML
1819
1820---++ ObjectMethod haveAccess($mode, $cUID) -> $boolean
1821
1822 * =$mode= - 'VIEW', 'CHANGE', 'CREATE', etc. (defaults to VIEW)
1823 * =$cUID= - Canonical user id (defaults to current user)
1824Check if the user has the given mode of access to the topic. This call
1825may result in the topic being read.
1826
1827=cut
1828
1829
# spent 60.9s (2.50ms+60.9) within Foswiki::Meta::haveAccess which was called 56 times, avg 1.09s/call: # 23 times (890µs+1.71ms) by Foswiki::MetaCache::get at line 234 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/MetaCache.pm, avg 113µs/call # 20 times (982µs+36.2ms) by Foswiki::INCLUDE at line 207 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/INCLUDE.pm, avg 1.86ms/call # 6 times (254µs+42.5ms) by Foswiki::REVINFO at line 32 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/REVINFO.pm, avg 7.13ms/call # 3 times (154µs+7.62ms) by Foswiki::Templates::_readTemplateFile at line 517 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Templates.pm, avg 2.59ms/call # once (85µs+60.8s) by Foswiki::Func::checkAccessPermission at line 1372 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm # once (41µs+3.13ms) by Foswiki::Form::new at line 110 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Form.pm # once (50µs+2.23ms) by Foswiki::UI::checkAccess at line 527 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI.pm # once (48µs+1.67ms) by Foswiki::Render::renderFORMFIELD at line 1032 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm
sub haveAccess {
183056162µs my ( $this, $mode, $cUID ) = @_;
18315673µs $mode ||= 'VIEW';
183256153µs $cUID ||= $this->{_session}->{user};
1833
18345687µs my $session = $this->{_session};
1835
183656830µs11260.9s my $ok = $session->access->haveAccess( $mode, $cUID, $this );
# spent 60.9s making 56 calls to Foswiki::Access::TopicACLAccess::haveAccess, avg 1.09s/call # spent 13.0ms making 56 calls to Foswiki::access, avg 233µs/call
183756699µs112860µs $reason = $session->access->getReason();
# spent 435µs making 56 calls to Foswiki::access, avg 8µs/call # spent 425µs making 56 calls to Foswiki::Access::getReason, avg 8µs/call
183856384µs return $ok;
1839}
1840
1841=begin TML
1842
1843---++ ObjectMethod save( %options )
1844
1845Save the current object, invoking appropriate plugin handlers
1846 * =%options= - Hash of options, see saveAs for list of keys
1847
1848=cut
1849
1850# SMELL: arguably save should only be permitted if the loaded rev of
1851# the object is the same as the latest rev.
1852sub save {
1853 my $this = shift;
1854 ASSERT( scalar(@_) % 2 == 0 ) if DEBUG;
1855 my %opts = @_;
1856 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
1857 if DEBUG;
1858
1859 my $plugins = $this->{_session}->{plugins};
1860
1861 # make sure version and date in TOPICINFO are up-to-date
1862 # (side effect of getRevisionInfo)
1863 $this->getRevisionInfo();
1864
1865 # Semantics inherited from Cairo. See
1866 # Foswiki:Codev.BugBeforeSaveHandlerBroken
1867 if ( $plugins->haveHandlerFor('beforeSaveHandler') ) {
1868
1869 # Break up the tom and write the meta into the topic text.
1870 # Nasty compatibility requirement as some old plugins may hack the
1871 # meta instead of using the Meta API
1872 my $text = $this->getEmbeddedStoreForm();
1873
1874 my $pretext = $text; # text before the handler modifies it
1875 my $premeta = $this->stringify(); # just the meta, no text
1876
1877 $plugins->dispatch( 'beforeSaveHandler', $text, $this->{_topic},
1878 $this->{_web}, $this );
1879
1880 # If the text has changed; it may be a text or meta change, or both
1881 if ( $text ne $pretext ) {
1882
1883 # Create a new object to parse the changed text
1884 my $after =
1885 new Foswiki::Meta( $this->{_session}, $this->{_web},
1886 $this->{_topic}, $text );
1887 unless ( $this->stringify() ne $premeta ) {
1888
1889 # Meta-data changes in the object take priority over
1890 # conflicting changes in the text. So if there have been
1891 # *any* changes in the meta, ignore changes in the text.
1892 $this->copyFrom($after);
1893 }
1894 $this->text( $after->text() );
1895 }
1896 }
1897
1898 my $signal;
1899 my $newRev;
1900 try {
1901 $newRev = $this->saveAs( $this->{_web}, $this->{_topic}, %opts );
1902 }
1903 catch Error::Simple with {
1904 $signal = shift;
1905 };
1906
1907 # Semantics inherited from TWiki. See
1908 # TWiki:Codev.BugBeforeSaveHandlerBroken
1909 if ( $plugins->haveHandlerFor('afterSaveHandler') ) {
1910 my $text = $this->getEmbeddedStoreForm();
1911 my $error = $signal ? $signal->{-text} : undef;
1912 $plugins->dispatch( 'afterSaveHandler', $text, $this->{_topic},
1913 $this->{_web}, $error, $this );
1914 }
1915
1916 throw $signal if $signal;
1917
1918 my @extras = ();
1919 push( @extras, 'minor' ) if $opts{minor}; # don't notify
1920 push( @extras, 'dontlog' ) if $opts{dontlog}; # don't statisticify
1921
1922 $this->{_session}->logEvent(
1923 'save',
1924 $this->{_web} . '.' . $this->{_topic},
1925 join( ', ', @extras ),
1926 $this->{_session}->{user}
1927 );
1928
1929 return $newRev;
1930}
1931
1932=begin TML
1933
1934---++ ObjectMethod saveAs( $web, $topic, %options ) -> $rev
1935
1936Save the current topic to a store location. Only works on topics.
1937*without* invoking plugins handlers.
1938 * =$web.$topic= - where to move to
1939 * =%options= - Hash of options, may include:
1940 * =forcenewrevision= - force an increment in the revision number,
1941 even if content doesn't change.
1942 * =dontlog= - don't include this change in statistics
1943 * =minor= - don't notify this change
1944 * =savecmd= - Save command (core use only)
1945 * =forcedate= - force the revision date to be this (core only)
1946 * =author= - cUID of author of change (core only - default current user)
1947
1948Note that the %options are passed on verbatim from Foswiki::Func::saveTopic,
1949so an extension author can in fact use all these options. However those
1950marked "core only" are for core use only and should *not* be used in
1951extensions.
1952
1953Returns the saved revision number.
1954
1955=cut
1956
1957# SMELL: arguably save should only be permitted if the loaded rev
1958# of the object is the same as the latest rev.
1959sub saveAs {
1960 my $this = shift;
1961 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
1962 if DEBUG;
1963
1964 my $newWeb = shift;
1965 my $newTopic = shift;
1966 ASSERT( scalar(@_) % 2 == 0 ) if DEBUG;
1967 my %opts = @_;
1968 my $cUID = $opts{author} || $this->{_session}->{user};
1969 $this->{_web} = $newWeb if $newWeb;
1970 $this->{_topic} = $newTopic if $newTopic;
1971 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
1972 if DEBUG;
1973
1974 unless ( $this->{_topic} eq $Foswiki::cfg{WebPrefsTopicName} ) {
1975
1976 # Don't verify web existance for WebPreferences, as saving
1977 # WebPreferences creates the web.
1978 unless ( $this->{_session}->{store}->webExists( $this->{_web} ) ) {
1979 throw Error::Simple( 'Unable to save topic '
1980 . $this->{_topic}
1981 . ' - web '
1982 . $this->{_web}
1983 . ' does not exist' );
1984 }
1985 }
1986
1987 $this->_atomicLock($cUID);
1988 my $i = $this->{_session}->{store}->getRevisionHistory($this);
1989 my $currentRev = $i->hasNext() ? $i->next() : 1;
1990 try {
1991 if ( $currentRev && !$opts{forcenewrevision} ) {
1992
1993 # See if we want to replace the existing top revision
1994 my $mtime1 =
1995 $this->{_session}->{store}
1996 ->getApproxRevTime( $this->{_web}, $this->{_topic} );
1997 my $mtime2 = time();
1998 my $dt = abs( $mtime2 - $mtime1 );
1999 if ( $dt <= $Foswiki::cfg{ReplaceIfEditedAgainWithin} ) {
2000 my $info = $this->{_session}->{store}->getVersionInfo($this);
2001
2002 # same user?
2003 if ( $info->{author} eq $cUID ) {
2004
2005 # reprev is required so we can tell when a merge is
2006 # based on something that is *not* the original rev
2007 # where another users' edit started.
2008 $info->{reprev} = $info->{version};
2009 $info->{date} = $opts{forcedate} || time();
2010 $this->setRevisionInfo(%$info);
2011 $this->{_session}->{store}->repRev( $this, $cUID, %opts );
2012 $this->{_loadedRev} = $currentRev;
2013 return $currentRev;
2014 }
2015 }
2016 }
2017 my $nextRev = $this->{_session}->{store}->getNextRevision($this);
2018 $this->setRevisionInfo(
2019 date => $opts{forcedate} || time(),
2020 author => $cUID,
2021 version => $nextRev,
2022 );
2023
2024 my $checkSave =
2025 $this->{_session}->{store}->saveTopic( $this, $cUID, \%opts );
2026 ASSERT( $checkSave == $nextRev, "$checkSave != $nextRev" ) if DEBUG;
2027 $this->{_loadedRev} = $nextRev;
2028 $this->{_latestIsLoaded} = 1;
2029 }
2030 finally {
2031 $this->_atomicUnlock($cUID);
2032 $this->fireDependency();
2033 };
2034 return $this->{_loadedRev};
2035}
2036
2037# An atomic lock will cause other
2038# processes that also try to claim a lock to block. A lock has a
2039# maximum lifetime of 2 minutes, so operations on a locked topic
2040# must be completed within that time. You cannot rely on the
2041# lock timeout clearing the lock, though; that should always
2042# be done by calling _atomicUnlock. The best thing to do is to guard
2043# the locked section with a try..finally clause. See man Error for more info.
2044#
2045# Atomic locks are _not_ the locks used when a topic is edited; those are
2046# Leases.
2047
2048sub _atomicLock {
2049 my ( $this, $cUID ) = @_;
2050 if ( $this->{_topic} ) {
2051 my $logger = $this->{_session}->logger();
2052 while (1) {
2053 my ( $user, $time ) =
2054 $this->{_session}->{store}->atomicLockInfo($this);
2055 last if ( !$user || $cUID eq $user );
2056 $logger->log( 'warning',
2057 'Lock on '
2058 . $this->getPath() . ' for '
2059 . $cUID
2060 . " denied by $user" );
2061
2062 # see how old the lock is. If it's older than 2 minutes,
2063 # break it anyway. Locks are atomic, and should never be
2064 # held that long, by _any_ process.
2065 if ( time() - $time > 2 * 60 ) {
2066 $logger->log( 'warning',
2067 $cUID . " broke ${user}s lock on " . $this->getPath() );
2068 $this->{_session}->{store}->atomicUnlock( $this, $cUID );
2069 last;
2070 }
2071
2072 # wait a couple of seconds before trying again
2073 sleep(2);
2074 }
2075
2076 # Topic
2077 $this->{_session}->{store}->atomicLock( $this, $cUID );
2078 }
2079 else {
2080
2081 # Web: Recursively lock subwebs and topics
2082 my $it = $this->eachWeb();
2083 while ( $it->hasNext() ) {
2084 my $web = $this->{_web} . '/' . $it->next();
2085 my $meta = $this->new( $this->{_session}, $web );
2086 $meta->_atomicLock($cUID);
2087 }
2088 $it = $this->eachTopic();
2089 while ( $it->hasNext() ) {
2090 my $meta =
2091 $this->new( $this->{_session}, $this->{_web}, $it->next() );
2092 $meta->_atomicLock($cUID);
2093 }
2094 }
2095}
2096
2097sub _atomicUnlock {
2098 my ( $this, $cUID ) = @_;
2099 if ( $this->{_topic} ) {
2100 $this->{_session}->{store}->atomicUnlock($this);
2101 }
2102 else {
2103 my $it = $this->eachWeb();
2104 while ( $it->hasNext() ) {
2105 my $web = $this->{_web} . '/' . $it->next();
2106 my $meta = $this->new( $this->{_session}, $web );
2107 $meta->_atomicUnlock($cUID);
2108 }
2109 $it = $this->eachTopic();
2110 while ( $it->hasNext() ) {
2111 my $meta =
2112 $this->new( $this->{_session}, $this->{_web}, $it->next() );
2113 $meta->_atomicUnlock($cUID);
2114 }
2115 }
2116}
2117
2118=begin TML
2119
2120---++ ObjectMethod move($to, %opts)
2121
2122Move this object (web or topic) to a store location specified by the
2123object $to. %opts may include:
2124 * =user= - cUID of the user doing the moving.
2125
2126=cut
2127
2128# will assert false if the loaded rev of the object is not
2129# the latest rev.
2130sub move {
2131 my ( $this, $to, %opts ) = @_;
2132 ASSERT( $this->{_web}, 'this is not a movable object' ) if DEBUG;
2133 ASSERT( $to->isa('Foswiki::Meta') && $to->{_web},
2134 'to is not a moving target' )
2135 if DEBUG;
2136
2137 my $cUID = $opts{user} || $this->{_session}->{user};
2138
2139 if ( $this->{_topic} ) {
2140
2141 # Move topic
2142
2143 $this->_atomicLock($cUID);
2144 $to->_atomicLock($cUID);
2145
2146 # Ensure latest rev is loaded
2147 my $from;
2148 if ( $this->latestIsLoaded() ) {
2149 $from = $this;
2150 }
2151 else {
2152 $from = $this->load();
2153 }
2154
2155 # Clear outstanding leases. We assume that the caller has checked
2156 # that the lease is OK to kill.
2157 $from->clearLease() if $from->getLease();
2158 try {
2159 $from->put(
2160 'TOPICMOVED',
2161 {
2162 from => $from->getPath(),
2163 to => $to->getPath(),
2164 date => time(),
2165 by => $cUID,
2166 }
2167 );
2168
2169 # save the metadata change without logging
2170 $this->saveAs(
2171 $this->{_web}, $this->{_topic},
2172 dontlog => 1, # no statistics
2173 );
2174 $from->{_session}->{store}->moveTopic( $from, $to, $cUID );
2175 $to->loadVersion();
2176 ASSERT( defined($to) and defined( $to->{_loadedRev} ) ) if DEBUG;
2177 }
2178 finally {
2179 $from->_atomicUnlock($cUID);
2180 $to->_atomicUnlock($cUID);
2181 $from->fireDependency();
2182 $to->fireDependency();
2183 };
2184
2185 }
2186 else {
2187
2188 # Move web
2189 ASSERT( !$this->{_session}->{store}->webExists( $to->{_web} ),
2190 "$to->{_web} does not exist" )
2191 if DEBUG;
2192 $this->_atomicLock($cUID);
2193 $this->{_session}->{store}->moveWeb( $this, $to, $cUID );
2194
2195 # No point in unlocking $this - it's moved!
2196 $to->_atomicUnlock($cUID);
2197 }
2198
2199 # Log rename
2200 my $old = $this->{_web} . '.' . ( $this->{_topic} || '' );
2201 my $new = $to->{_web} . '.' . ( $to->{_topic} || '' );
2202 $this->{_session}
2203 ->logEvent( 'rename', $old, "moved to $new", $this->{_session}->{user} );
2204
2205 # alert plugins of topic move
2206 $this->{_session}->{plugins}
2207 ->dispatch( 'afterRenameHandler', $this->{_web}, $this->{_topic} || '',
2208 '', $to->{_web}, $to->{_topic} || '', '' );
2209}
2210
2211=begin TML
2212
2213---++ ObjectMethod deleteMostRecentRevision(%opts)
2214Delete (or elide) the most recent revision of this. Only works on topics.
2215
2216=%opts= may include
2217 * =user= - cUID of user doing the unlocking
2218
2219=cut
2220
2221sub deleteMostRecentRevision {
2222 my ( $this, %opts ) = @_;
2223 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
2224 if DEBUG;
2225 my $rev;
2226 my $cUID = $opts{user} || $this->{_session}->{user};
2227
2228 $this->_atomicLock($cUID);
2229 try {
2230 $rev = $this->{_session}->{store}->delRev( $this, $cUID );
2231 }
2232 finally {
2233 $this->_atomicUnlock($cUID);
2234 };
2235
2236 # TODO: delete entry in .changes
2237
2238 # write log entry
2239 $this->{_session}->logEvent(
2240 'cmd',
2241 $this->{_web} . '.' . $this->{_topic},
2242 "delRev $rev by " . $this->{_session}->{user}
2243 );
2244}
2245
2246=begin TML
2247
2248---++ ObjectMethod replaceMostRecentRevision( %opts )
2249Replace the most recent revision with whatever is in the memory copy.
2250Only works on topics.
2251
2252%opts may include:
2253 * =forcedate= - try and re-use the date of the original check
2254 * =user= - cUID of the user doing the action
2255
2256=cut
2257
2258sub replaceMostRecentRevision {
2259 my $this = shift;
2260 my %opts = @_;
2261 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
2262 if DEBUG;
2263
2264 my $cUID = $opts{user} || $this->{_session}->{user};
2265
2266 $this->_atomicLock($cUID);
2267
2268 my $info = $this->getRevisionInfo();
2269
2270 if ( $opts{forcedate} ) {
2271
2272 # We are trying to force the rev to be saved with the same date
2273 # and user as the prior rev. However, exactly the same date may
2274 # cause some revision control systems to barf, so to avoid this we
2275 # add 1 minute to the rev time. Note that this mode of operation
2276 # will normally require sysadmin privilege, as it can result in
2277 # confused rev dates if abused.
2278 $info->{date} += 60;
2279 }
2280 else {
2281
2282 # use defaults (current time, current user)
2283 $info->{date} = time();
2284 $info->{author} = $cUID;
2285 }
2286
2287 # repRev is required so we can tell when a merge is based on something
2288 # that is *not* the original rev where another users' edit started.
2289 $info->{reprev} = $info->{version};
2290 $this->setRevisionInfo(%$info);
2291
2292 try {
2293 $this->{_session}->{store}->repRev( $this, $cUID, @_ );
2294 }
2295 finally {
2296 $this->_atomicUnlock($cUID);
2297 };
2298
2299 # write log entry
2300 require Foswiki::Time;
2301 my @extras = ( $info->{version} );
2302 push( @extras,
2303 Foswiki::Time::formatTime( $info->{date}, '$rcs', 'gmtime' ) );
2304 push( @extras, 'minor' ) if $opts{minor};
2305 push( @extras, 'dontlog' ) if $opts{dontlog};
2306 push( @extras, 'forced' ) if $opts{forcedate};
2307 $this->{_session}
2308 ->logEvent( 'reprev', $this->getPath(), join( ', ', @extras ), $cUID );
2309}
2310
2311=begin TML
2312
2313---++ ObjectMethod getRevisionHistory([$attachment]) -> $iterator
2314
2315Get an iterator over the range of version identifiers (just the identifiers,
2316not the content).
2317
2318The iterator will be empty ($iterator->hasNext() will be false) if the object
2319does not exist.
2320
2321$attachment is optional.
2322
2323Not valid on webs.
2324
2325=cut
2326
2327
# spent 621ms (78µs+621) within Foswiki::Meta::getRevisionHistory which was called 2 times, avg 310ms/call: # once (30µs+312ms) by Foswiki::UI::View::revisionsAround at line 437 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm # once (48µs+308ms) by Foswiki::UI::View::view at line 118 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm
sub getRevisionHistory {
232824µs my ( $this, $attachment ) = @_;
2329211µs28µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 8µs making 2 calls to Assert::ASSERTS_OFF, avg 4µs/call
2330 if DEBUG;
2331
2332# if ((not defined($attachment)) and ($this->{_latestIsLoaded})) {
2333# #why poke around in revision history (slow) if we 'have the latest'
2334# return new Foswiki::Iterator::NumberRangeIterator( $this->{_loadedRev}, 1 );
2335# }
2336
2337265µs2621ms return $this->{_session}->{store}->getRevisionHistory( $this, $attachment );
# spent 621ms making 2 calls to Foswiki::Store::VC::Store::getRevisionHistory, avg 310ms/call
2338}
2339
2340=begin TML
2341
2342---++ ObjectMethod getLatestRev[$attachment]) -> $revision
2343
2344Get the revision ID of the latest revision.
2345
2346$attachment is optional.
2347
2348Not valid on webs.
2349
2350Returns an integer revision number > 0 if the object exists.
2351
2352Returns 0 if the object does not exist.
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 32µs (28+4) within Foswiki::Meta::latestIsLoaded which was called: # once (28µs+4µs) by Foswiki::QUERY at line 22 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/QUERY.pm
sub latestIsLoaded {
237512µs my $this = shift;
237615µs14µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 4µs making 1 call to Assert::ASSERTS_OFF
2377 if DEBUG;
237812µs return $this->{_latestIsLoaded} if defined $this->{_latestIsLoaded};
2379112µs return defined $this->{_loadedRev}
2380 && $this->{_loadedRev} == $this->getLatestRev();
2381}
2382
2383=begin TML
2384
2385---++ ObjectMethod getLoadedRev() -> $integer
2386
2387Get the currently loaded revision. Result will be a revision number, or
2388undef if no revision has been loaded. Only valid on topics.
2389
2390WARNING: some store implementations use the concept of a "working copy" of
2391each topic that may be modified *without* being added to the revision
2392control system. This means that the version number reported for the latest
2393rev may not be the actual latest version.
2394
2395=cut
2396
2397
# spent 196µs (165+31) within Foswiki::Meta::getLoadedRev which was called 9 times, avg 22µs/call: # 6 times (97µs+20µs) by Foswiki::REVINFO at line 21 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/REVINFO.pm, avg 19µs/call # 3 times (68µs+11µs) by Foswiki::META at line 16 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Macros/META.pm, avg 26µs/call
sub getLoadedRev {
2398915µs my $this = shift;
2399943µs931µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 31µs making 9 calls to Assert::ASSERTS_OFF, avg 3µs/call
2400 if DEBUG;
2401969µs return $this->{_loadedRev};
2402}
2403
2404=begin TML
2405
2406---++ ObjectMethod removeFromStore( $attachment )
2407 * =$attachment= - optional, provide to delete an attachment
2408
2409Use with great care! Removes all trace of the given web, topic
2410or attachment from the store, possibly including all its history.
2411
2412Also does not ensure consistency of the store
2413(for eg, if you delete an attachment, it does not update the intopic META)
2414
2415=cut
2416
2417sub removeFromStore {
2418 my ( $this, $attachment ) = @_;
2419 my $store = $this->{_session}->{store};
2420 ASSERT( $this->{_web}, 'this is not a removable object' ) if DEBUG;
2421
2422 if ( !$store->webExists( $this->{_web} ) ) {
2423 throw Error::Simple( 'No such web ' . $this->{_web} );
2424 }
2425 if ( $this->{_topic}
2426 && !$store->topicExists( $this->{_web}, $this->{_topic} ) )
2427 {
2428 throw Error::Simple(
2429 'No such topic ' . $this->{_web} . '.' . $this->{_topic} );
2430 }
2431
2432 if ( $attachment && !$this->hasAttachment($attachment) ) {
2433 ASSERT( $this->{topic}, 'this is not a removable object' ) if DEBUG;
2434 throw Error::Simple( 'No such attachment '
2435 . $this->{_web} . '.'
2436 . $this->{_topic} . '.'
2437 . $attachment );
2438 }
2439
2440 $store->remove( $this->{_session}->{user}, $this, $attachment );
2441}
2442
2443=begin TML
2444
2445---++ ObjectMethod getDifferences( $rev2, $contextLines ) -> \@diffArray
2446
2447Get the differences between the rev loaded into this object, and another
2448rev of the same topic. Return reference to an array of differences.
2449 * =$rev2= - the other revision to diff against
2450 * =$contextLines= - number of lines of context required
2451
2452Each difference is of the form [ $type, $right, $left ] where
2453| *type* | *Means* |
2454| =+= | Added |
2455| =-= | Deleted |
2456| =c= | Changed |
2457| =u= | Unchanged |
2458| =l= | Line Number |
2459
2460=cut
2461
2462sub getDifferences {
2463 my ( $this, $rev2, $contextLines ) = @_;
2464 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
2465 if DEBUG;
2466 return $this->{_session}->{store}
2467 ->getRevisionDiff( $this, $rev2, $contextLines );
2468}
2469
2470=begin TML
2471
2472---++ ObjectMethod getRevisionAtTime( $time ) -> $rev
2473 * =$time= - time (in epoch secs) for the rev
2474
2475Get the revision number for a topic at a specific time.
2476Returns a single-digit rev number or 0 if it couldn't be determined
2477(either because the topic isn't that old, or there was a problem)
2478
2479=cut
2480
2481sub getRevisionAtTime {
2482 my ( $this, $time ) = @_;
2483 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
2484 if DEBUG;
2485 return $this->{_session}->{store}->getRevisionAtTime( $this, $time );
2486}
2487
2488=begin TML
2489
2490---++ ObjectMethod setLease( $length )
2491
2492Take out an lease on the given topic for this user for $length seconds.
2493
2494See =getLease= for more details about Leases.
2495
2496=cut
2497
2498sub setLease {
2499 my ( $this, $length ) = @_;
2500 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
2501 if DEBUG;
2502 my $t = time();
2503 my $lease = {
2504 user => $this->{_session}->{user},
2505 expires => $t + $length,
2506 taken => $t
2507 };
2508 return $this->{_session}->{store}->setLease( $this, $lease );
2509}
2510
2511=begin TML
2512
2513---++ ObjectMethod getLease() -> $lease
2514
2515If there is an lease on the topic, return the lease, otherwise undef.
2516A lease is a block of meta-information about a topic that can be
2517recovered (this is a hash containing =user=, =taken= and =expires=).
2518Leases are taken out when a topic is edited. Only one lease
2519can be active on a topic at a time. Leases are used to warn if
2520another user is already editing a topic.
2521
2522=cut
2523
2524sub getLease {
2525 my $this = shift;
2526 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
2527 if DEBUG;
2528 return $this->{_session}->{store}->getLease($this);
2529}
2530
2531=begin TML
2532
2533---++ ObjectMethod clearLease()
2534
2535Cancel the current lease.
2536
2537See =getLease= for more details about Leases.
2538
2539=cut
2540
2541sub clearLease {
2542 my $this = shift;
2543 ASSERT(
2544 $this->{_web} && $this->{_topic},
2545 ( $this->{_web} || 'undef' ) . '.'
2546 . ( $this->{_topic} || 'undef' )
2547 . ' this is not a topic object'
2548 ) if DEBUG;
2549 $this->{_session}->{store}->setLease($this);
2550}
2551
2552=begin TML
2553
2554---++ ObjectMethod onTick($time)
2555
2556Method invoked at regular intervals, usually by a cron job. The job of
2557this method is to prod the store into cleaning up expired leases, and
2558any other admin job that needs doing at regular intervals.
2559
2560=cut
2561
2562sub onTick {
2563 my ( $this, $time ) = @_;
2564
2565 if ( !$this->{_topic} ) {
2566 my $it = $this->eachWeb();
2567 while ( $it->hasNext() ) {
2568 my $web = $it->next();
2569 $web = $this->getPath() . "/$web" if $this->getPath();
2570 my $m = $this->new( $this->{_session}, $web );
2571 $m->onTick($time);
2572 }
2573 if ( $this->{_web} ) {
2574 $it = $this->eachTopic();
2575 while ( $it->hasNext() ) {
2576 my $topic = $it->next();
2577 my $topicObject =
2578 $this->new( $this->{_session}, $this->getPath(), $topic );
2579 $topicObject->onTick($time);
2580 }
2581 }
2582
2583 # Clean up spurious leases that may have been left behind
2584 # during cancelled topic creation
2585 $this->{_session}->{store}->removeSpuriousLeases( $this->getPath() )
2586 if $this->getPath();
2587 }
2588 else {
2589 my $lease = $this->getLease();
2590 if ( $lease && $lease->{expires} < $time ) {
2591 $this->clearLease();
2592 }
2593 }
2594}
2595
2596############# ATTACHMENTS ON TOPICS #############
2597
2598=begin TML
2599
2600---++ ObjectMethod getAttachmentRevisionInfo($attachment, $rev) -> \%info
2601 * =$attachment= - attachment name
2602 * =$rev= - optional integer attachment revision number
2603Get revision info for an attachment. Only valid on topics.
2604
2605$info will contain at least: date, author, version, comment
2606
2607=cut
2608
2609sub getAttachmentRevisionInfo {
2610 my ( $this, $attachment, $fromrev ) = @_;
2611 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
2612 if DEBUG;
2613
2614 return $this->{_session}->{store}
2615 ->getAttachmentVersionInfo( $this, $fromrev, $attachment );
2616}
2617
2618=begin TML
2619
2620---++ ObjectMethod attach ( %opts )
2621
2622 * =%opts= may include:
2623 * =name= - Name of the attachment
2624 * =dontlog= - don't add to statistics
2625 * =comment= - comment for save
2626 * =hide= - if the attachment is to be hidden in normal topic view
2627 * =stream= - Stream of file to upload. Uses =file= if not set.
2628 * =file= - Name of a *server* file to use for the attachment
2629 data. This should be passed if it is known, as it may be used
2630 to optimise handler calls.
2631 * =filepath= - Optional. Client path to file.
2632 * =filesize= - Optional. Size of uploaded data.
2633 * =filedate= - Optional. Date of file.
2634 * =author= - Optional. cUID of author of change. Defaults to current.
2635 * =notopicchange= - Optional. if the topic is *not* to be modified.
2636 This may result in incorrect meta-data stored in the topic, so must
2637 be used with care. Only has a meaning if the store implementation
2638 stores meta-data in topics.
2639
2640Saves a new revision of the attachment, invoking plugin handlers as
2641appropriate. This method automatically updates the loaded rev of $this
2642to the latest topic revision.
2643
2644If neither of =stream= or =file= are set, this is a properties-only save.
2645
2646Throws an exception on error.
2647
2648=cut
2649
2650# SMELL: arguably should only be permitted if the loaded rev of the object is the same as the
2651# latest rev.
2652
2653sub attach {
2654 my $this = shift;
2655 my %opts = @_;
2656 my $action;
2657 my $plugins = $this->{_session}->{plugins};
2658 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
2659 if DEBUG;
2660
2661# make sure we don't save a half-loaded topic stub...which indeed - smell - is possible
2662 $this->loadVersion() unless $this->latestIsLoaded();
2663
2664 if ( $opts{file} && !$opts{stream} ) {
2665
2666 # no stream given, but a file was given; open it.
2667 open( $opts{stream}, '<', $opts{file} )
2668 || throw Error::Simple( 'Could not open ' . $opts{file} );
2669 binmode( $opts{stream} )
2670 || throw Error::Simple( $opts{file} . ' binmode failed: ' . $! );
2671 }
2672
2673 my $attrs;
2674 if ( $opts{stream} ) {
2675 $action = 'upload';
2676
2677 $attrs = {
2678 name => $opts{name},
2679 attachment => $opts{name},
2680 stream => $opts{stream},
2681 user => $this->{_session}->{user}, # cUID
2682 comment => defined $opts{comment} ? $opts{comment} : '',
2683 };
2684
2685 if ( $plugins->haveHandlerFor('beforeAttachmentSaveHandler') ) {
2686
2687 # *Deprecated* handler.
2688
2689 # The handler may have been called as a result of an upload,
2690 # in which case the data is already in a file in the CGI cache,
2691 # and the stream is valid, or it may be been arrived at via a
2692 # call to Func::saveAttachment, in which case it's possible that
2693 # the stream isn't open but we have a tmpFilename instead.
2694 #
2695 $attrs->{tmpFilename} = $opts{file};
2696
2697 if ( !defined( $attrs->{tmpFilename} ) ) {
2698
2699 # CGI (or the caller) did not provide a temporary file
2700
2701 # Stream the data to a temporary file, so it can be passed
2702 # to the handler.
2703
2704 require File::Temp;
2705
2706 my $fh = new File::Temp();
2707 binmode($fh);
2708
2709 # transfer 512KB blocks
2710 my $transfer;
2711 my $r;
2712 while ( $r = sysread( $opts{stream}, $transfer, 0x80000 ) ) {
2713 if ( !defined $r ) {
2714 next if ( $! == Errno::EINTR );
2715 die "system read error: $!\n";
2716 }
2717 my $offset = 0;
2718 while ($r) {
2719 my $w = syswrite( $fh, $transfer, $r, $offset );
2720 die "system write error: $!\n" unless ( defined $w );
2721 $offset += $w;
2722 $r -= $w;
2723 }
2724 }
2725 select( ( select($fh), $| = 1 )[0] );
2726
2727 # $fh->seek only in File::Temp 0.17 and later
2728 seek( $fh, 0, 0 ) or die "Can't seek temp: $!\n";
2729 $opts{stream} = $fh;
2730 $attrs->{tmpFilename} = $fh->filename();
2731 }
2732
2733 if ( $plugins->haveHandlerFor('beforeAttachmentSaveHandler') ) {
2734 $plugins->dispatch( 'beforeAttachmentSaveHandler', $attrs,
2735 $this->{_topic}, $this->{_web} );
2736 }
2737
2738 # Have to assume it's changed, even if it hasn't.
2739 open( $attrs->{stream}, '<', $attrs->{tmpFilename} )
2740 || die "Internal error: $!";
2741 binmode( $attrs->{stream} );
2742 $opts{stream} = $attrs->{stream};
2743
2744 delete $attrs->{tmpFilename};
2745 }
2746
2747 if ( $plugins->haveHandlerFor('beforeUploadHandler') ) {
2748
2749 # Check the stream is seekable
2750 ASSERT(
2751 seek( $attrs->{stream}, 0, 1 ),
2752 'Stream for attachment is not seekable'
2753 ) if DEBUG;
2754
2755 $plugins->dispatch( 'beforeUploadHandler', $attrs, $this );
2756 $opts{stream} = $attrs->{stream};
2757 seek( $opts{stream}, 0, 0 ); # seek to beginning
2758 binmode( $opts{stream} );
2759 }
2760
2761 # Force reload of the latest version
2762 $this = $this->load() unless $this->latestIsLoaded();
2763
2764 # Note that latestIsLoaded may still be false if the topic doesn't exist yet
2765
2766 my $error;
2767 try {
2768 $this->{_session}->{store}
2769 ->saveAttachment( $this, $opts{name}, $opts{stream},
2770 $opts{author} || $this->{_session}->{user},
2771 $opts{comment} );
2772 }
2773 finally {
2774 $this->fireDependency();
2775 };
2776
2777 my $fileVersion = $this->getLatestRev( $opts{name} );
2778 $attrs->{version} = $fileVersion;
2779 $attrs->{path} = $opts{filepath} if ( defined( $opts{filepath} ) );
2780 $attrs->{size} = $opts{filesize} if ( defined( $opts{filesize} ) );
2781 $attrs->{date} = defined $opts{filedate} ? $opts{filedate} : time();
2782
2783 if ( $plugins->haveHandlerFor('afterAttachmentSaveHandler') ) {
2784
2785 # *Deprecated* handler
2786 $plugins->dispatch( 'afterAttachmentSaveHandler', $attrs,
2787 $this->{_topic}, $this->{_web} );
2788 }
2789 }
2790 else {
2791
2792 # Property change
2793 $action = 'save';
2794 $attrs = $this->get( 'FILEATTACHMENT', $opts{name} );
2795 $attrs->{name} = $opts{name};
2796 $attrs->{comment} = $opts{comment} if ( defined( $opts{comment} ) );
2797 }
2798 $attrs->{attr} = ( $opts{hide} ) ? 'h' : '';
2799 delete $attrs->{stream};
2800 delete $attrs->{tmpFilename};
2801 $this->putKeyed( 'FILEATTACHMENT', $attrs );
2802
2803 if ( $opts{createlink} ) {
2804 my $text = $this->text();
2805 $text = '' unless defined $text;
2806 $text .=
2807 $this->{_session}->attach->getAttachmentLink( $this, $opts{name} );
2808 $this->text($text);
2809 }
2810
2811 $this->saveAs() unless $opts{notopicchange};
2812
2813 my @extras = ( $opts{name} );
2814 push( @extras, 'dontlog' ) if $opts{dontlog}; # no statistics
2815 $this->{_session}->logEvent(
2816 $action,
2817 $this->{_web} . '.' . $this->{_topic},
2818 join( ', ', @extras ),
2819 $this->{_session}->{user}
2820 );
2821
2822 if ( $plugins->haveHandlerFor('afterUploadHandler') ) {
2823 $plugins->dispatch( 'afterUploadHandler', $attrs, $this );
2824 }
2825}
2826
2827=begin TML
2828
2829---++ ObjectMethod hasAttachment( $name ) -> $boolean
2830Test if the named attachment exists. Only valid on topics. The attachment
2831must exist in the store (it is not sufficient for it to be referenced
2832in the object only)
2833
2834=cut
2835
2836sub hasAttachment {
2837 my ( $this, $name ) = @_;
2838 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
2839 if DEBUG;
2840 return $this->{_session}->{store}->attachmentExists( $this, $name );
2841}
2842
2843=begin TML
2844
2845---++ ObjectMethod testAttachment( $name, $test ) -> $value
2846
2847Performs a type test on the given attachment file.
2848 * =$name= - name of the attachment to test e.g =lolcat.gif=
2849 * =$test= - the test to perform e.g. ='r'=
2850
2851The return value is the value that would be returned by the standard
2852perl file operations, as indicated by $type
2853
2854 * r File is readable by current user (tests Foswiki VIEW permission)
2855 * w File is writable by current user (tests Foswiki CHANGE permission)
2856 * e File exists.
2857 * z File has zero size.
2858 * s File has nonzero size (returns size).
2859 * T File is an ASCII text file (heuristic guess).
2860 * B File is a "binary" file (opposite of T).
2861 * M Last modification time (epoch seconds).
2862 * A Last access time (epoch seconds).
2863
2864Note that all these types should behave as the equivalent standard perl
2865operator behaves, except M and A which are independent of the script start
2866time (see perldoc -f -X for more information)
2867
2868Other standard Perl file tests may also be supported on some store
2869implementations, but cannot be relied on.
2870
2871Errors will be signalled by an Error::Simple exception.
2872
2873=cut
2874
2875sub testAttachment {
2876 my ( $this, $attachment, $test ) = @_;
2877 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
2878 if DEBUG;
2879
2880 $this->addDependency();
2881
2882 $test =~ /(\w)/;
2883 $test = $1;
2884 if ( $test eq 'r' ) {
2885 return $this->haveAccess('VIEW');
2886 }
2887 elsif ( $test eq 'w' ) {
2888 return $this->haveAccess('CHANGE');
2889 }
2890
2891 return
2892 return $this->{_session}->{store}
2893 ->testAttachment( $this, $attachment, $test );
2894}
2895
2896=begin TML
2897
2898---+++ openAttachment($attachment, $mode, %opts) -> $fh
2899 * =$attachment= - the attachment
2900 * =$mode= - mode to open the attachment in
2901Opens a stream onto the attachment. This method is primarily to
2902support virtual file systems, and as such access controls are *not*
2903checked, plugin handlers are *not* called, and it does *not* update the
2904meta-data in the topicObject.
2905
2906=$mode= can be '&lt;', '&gt;' or '&gt;&gt;' for read, write, and append
2907respectively.
2908
2909=%opts= can take different settings depending on =$mode=.
2910 * =$mode='&lt;'=
2911 * =version= - revision of the object to open e.g. =version => 6=
2912 * =$mode='&gt;'= or ='&gt;&gt;'
2913 * no options
2914Errors will be signalled by an =Error= exception.
2915
2916See also =attach= if this function is too basic for you.
2917
2918=cut
2919
2920sub openAttachment {
2921 my ( $this, $attachment, $mode, @opts ) = @_;
2922 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
2923 if DEBUG;
2924
2925 return $this->{_session}->{store}
2926 ->openAttachment( $this, $attachment, $mode, @opts );
2927
2928}
2929
2930=begin TML
2931
2932---++ ObjectMethod moveAttachment( $name, $to, %opts ) -> $data
2933Move the named attachment to the topic indicates by $to.
2934=%opts= may include:
2935 * =new_name= - new name for the attachment
2936 * =user= - cUID of user doing the moving
2937
2938=cut
2939
2940sub moveAttachment {
2941 my $this = shift;
2942 my $name = shift;
2943 my $to = shift;
2944 my %opts = @_;
2945 my $cUID = $opts{user} || $this->{_session}->{user};
2946 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
2947 if DEBUG;
2948 ASSERT( $to->{_web} && $to->{_topic}, 'to is not a topic object' ) if DEBUG;
2949
2950 my $newName = $opts{new_name} || $name;
2951
2952 # Make sure we have latest revs
2953 $this = $this->load() unless $this->latestIsLoaded();
2954
2955 $this->_atomicLock($cUID);
2956 $to->_atomicLock($cUID);
2957
2958 try {
2959 $this->{_session}->{store}
2960 ->moveAttachment( $this, $name, $to, $newName, $cUID );
2961
2962 # Modify the cache of the old topic
2963 my $fileAttachment = $this->get( 'FILEATTACHMENT', $name );
2964 $this->remove( 'FILEATTACHMENT', $name );
2965 $this->saveAs(
2966 undef, undef,
2967 dontlog => 1, # no statistics
2968 comment => 'lost ' . $name
2969 );
2970
2971 # Add file attachment to new topic
2972 $fileAttachment->{name} = $newName;
2973 $fileAttachment->{movefrom} = $this->getPath() . '.' . $name;
2974 $fileAttachment->{moveby} =
2975 $this->{_session}->{users}->getLoginName($cUID);
2976 $fileAttachment->{movedto} = $to->getPath() . '.' . $newName;
2977 $fileAttachment->{movedwhen} = time();
2978 $to->loadVersion();
2979 $to->putKeyed( 'FILEATTACHMENT', $fileAttachment );
2980
2981 if ( $this->getPath() eq $to->getPath() ) {
2982 $to->remove( 'FILEATTACHMENT', $name );
2983 }
2984
2985 $to->saveAs(
2986 undef, undef,
2987 dontlog => 1, # no statistics
2988 comment => 'gained' . $newName
2989 );
2990 }
2991 finally {
2992 $to->_atomicUnlock($cUID);
2993 $this->_atomicUnlock($cUID);
2994 $this->fireDependency();
2995 $to->fireDependency();
2996 };
2997
2998 # alert plugins of attachment move
2999 $this->{_session}->{plugins}
3000 ->dispatch( 'afterRenameHandler', $this->{_web}, $this->{_topic}, $name,
3001 $to->{_web}, $to->{_topic}, $newName );
3002
3003 $this->{_session}->logEvent(
3004 'move',
3005 $this->getPath() . '.'
3006 . $name
3007 . ' moved to '
3008 . $to->getPath() . '.'
3009 . $newName,
3010 $cUID
3011 );
3012}
3013
3014=begin TML
3015
3016---++ ObjectMethod copyAttachment( $name, $to, %opts ) -> $data
3017Copy the named attachment to the topic indicates by $to.
3018=%opts= may include:
3019 * =new_name= - new name for the attachment
3020 * =user= - cUID of user doing the moving
3021
3022=cut
3023
3024sub copyAttachment {
3025 my $this = shift;
3026 my $name = shift;
3027 my $to = shift;
3028 my %opts = @_;
3029 my $cUID = $opts{user} || $this->{_session}->{user};
3030 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
3031 if DEBUG;
3032 ASSERT( $to->{_web} && $to->{_topic}, 'to is not a topic object' ) if DEBUG;
3033
3034 my $newName = $opts{new_name} || $name;
3035
3036 # Make sure we have latest revs
3037 my $from;
3038 if ( $this->latestIsLoaded() ) {
3039 $from = $this;
3040 }
3041 else {
3042 $from = $this->load();
3043 }
3044
3045 $from->_atomicLock($cUID);
3046 $to->_atomicLock($cUID);
3047
3048 try {
3049 $from->{_session}->{store}
3050 ->copyAttachment( $from, $name, $to, $newName, $cUID );
3051
3052 # Add file attachment to new topic by copying the old one
3053 my $fileAttachment = { %{ $from->get( 'FILEATTACHMENT', $name ) } };
3054 $fileAttachment->{name} = $newName;
3055
3056 $to->loadVersion() unless $to->latestIsLoaded();
3057 $to->putKeyed( 'FILEATTACHMENT', $fileAttachment );
3058
3059 if ( $from->getPath() eq $to->getPath() ) {
3060 $to->remove( 'FILEATTACHMENT', $name );
3061 }
3062
3063 $to->saveAs(
3064 undef, undef,
3065 author => $cUID,
3066 dontlog => 1, # no statistics
3067 comment => 'gained' . $newName
3068 );
3069 }
3070 finally {
3071 $to->_atomicUnlock($cUID);
3072 $from->_atomicUnlock($cUID);
3073 $from->fireDependency();
3074 $to->fireDependency();
3075 };
3076
3077 # alert plugins of attachment move
3078 # SMELL: no defined handler for attachment copies
3079 # $this->{_session}->{plugins}
3080 # ->dispatch( 'afterCopyHandler', $this->{_web}, $this->{_topic}, $name,
3081 # $to->{_web}, $to->{_topic}, $newName );
3082
3083 $this->{_session}->logEvent(
3084 'copy',
3085 $this->getPath() . '.'
3086 . $name
3087 . ' copied to '
3088 . $to->getPath() . '.'
3089 . $newName,
3090 $cUID
3091 );
3092}
3093
3094=begin TML
3095
3096---++ ObjectMethod expandNewTopic()
3097Expand only that subset of Foswiki variables that are
3098expanded during topic creation, in the body text and
3099PREFERENCE meta only.
3100
3101The expansion is in-place in the object data.
3102
3103Only valid on topics.
3104
3105=cut
3106
3107sub expandNewTopic {
3108 my ($this) = @_;
3109 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
3110 if DEBUG;
3111 $this->{_session}->expandMacrosOnTopicCreation($this);
3112}
3113
3114=begin TML
3115
3116---++ ObjectMethod expandMacros( $text ) -> $text
3117Expand only all Foswiki variables that are
3118expanded during topic view. Returns the expanded text.
3119Only valid on topics.
3120
3121=cut
3122
3123
# spent 183s (20.9ms+183) within Foswiki::Meta::expandMacros which was called 1131 times, avg 162ms/call: # 1094 times (20.1ms+-17.8ms) by Foswiki::Func::expandCommonVariables at line 2474 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm, avg 2µs/call # 28 times (564µs+-564µs) by Foswiki::Form::_parseFormDefinition at line 238 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Form.pm, avg 0s/call # 4 times (79µs+9.49ms) by Foswiki::Render::_renderNonExistingWikiWord at line 707 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Render.pm, avg 2.39ms/call # 3 times (110µs+183s) by Foswiki::UI::View::_prepare at line 409 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm, avg 60.9s/call # 2 times (43µs+32.2ms) by Foswiki::_renderZone at line 3495 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm, avg 16.1ms/call
sub expandMacros {
312411313.05ms my ( $this, $text ) = @_;
312511315.03ms11313.83ms ASSERT( defined( $this->web ) && defined( $this->topic ),
# spent 3.83ms making 1131 calls to Assert::ASSERTS_OFF, avg 3µs/call
3126 'this is not a topic object' )
3127 if DEBUG;
3128
3129113110.4ms1131183s return $this->{_session}->expandMacros( $text, $this );
# spent 185s making 1131 calls to Foswiki::expandMacros, avg 164ms/call, recursion: max depth 1, sum of overlapping time 2.39s
3130}
3131
3132=begin TML
3133
3134---++ ObjectMethod renderTML( $text ) -> $text
3135Render all TML constructs in the text into HTML. Returns the rendered text.
3136Only valid on topics.
3137
3138=cut
3139
3140
# spent 191ms (225µs+191) within Foswiki::Meta::renderTML which was called 5 times, avg 38.2ms/call: # 3 times (169µs+167ms) by Foswiki::UI::View::_prepare at line 410 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/View.pm, avg 55.9ms/call # 2 times (56µs+23.2ms) by Foswiki::_renderZone at line 3496 of /usr/local/src/github.com/foswiki/core/lib/Foswiki.pm, avg 11.6ms/call
sub renderTML {
3141541µs my ( $this, $text ) = @_;
3142528µs521µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 21µs making 5 calls to Assert::ASSERTS_OFF, avg 4µs/call
3143 if DEBUG;
31445121µs10191ms return $this->{_session}->renderer->getRenderedVersion( $text, $this );
# spent 191ms making 5 calls to Foswiki::Render::getRenderedVersion, avg 38.1ms/call # spent 50µs making 5 calls to Foswiki::renderer, avg 10µs/call
3145}
3146
3147=begin TML
3148
3149---++ ObjectMethod summariseText( $flags [, $text, \%searchOptions] ) -> $tml
3150
3151Makes a plain text summary of the topic text by simply trimming a bit
3152off the top. Truncates to $TMTRUNC chars or, if a number is specified
3153in $flags, to that length.
3154
3155If $text is defined, use it in place of the topic text.
3156
3157The =\%searchOptions= hash may contain the following options:
3158 * =type= - search type: keyword, literal, query
3159 * =casesensitive= - false to ignore case (default true)
3160 * =wordboundaries= - if type is 'keyword'
3161 * =tokens= - array ref of search tokens
3162
3163TODO: should this really be in Meta? it seems like a rendering issue to me.
3164
3165
3166=cut
3167
3168sub summariseText {
3169 my ( $this, $flags, $text, $searchOptions ) = @_;
3170 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
3171 if DEBUG;
3172
3173 $flags ||= '';
3174
3175 $text = $this->text() unless defined $text;
3176 $text = '' unless defined $text;
3177
3178 my $plainText =
3179 $this->session->renderer->TML2PlainText( $text, $this, $flags );
3180 $plainText =~ s/\n+/ /g;
3181
3182 # limit to n chars
3183 my $limit = $flags || '';
3184 unless ( $limit =~ s/^.*?([0-9]+).*$/$1/ ) {
3185 $limit = $SUMMARY_TMLTRUNC;
3186 }
3187 $limit = $SUMMARY_MINTRUNC if ( $limit < $SUMMARY_MINTRUNC );
3188
3189 if ( $flags =~ m/searchcontext/ ) {
3190 return $this->_summariseTextWithSearchContext( $plainText, $limit,
3191 $searchOptions );
3192 }
3193 else {
3194 return $this->_summariseTextSimple( $plainText, $limit );
3195 }
3196}
3197
3198=begin TML
3199
3200---++ ObjectMethod _summariseTextSimple( $text, $limit ) -> $tml
3201
3202Makes a plain text summary of the topic text by simply trimming a bit
3203off the top. Truncates to $TMTRUNC chars or, if a number is specified
3204in $flags, to that length.
3205
3206TODO: should this really be in Meta? it seems like a rendering issue to me.
3207
3208=cut
3209
3210sub _summariseTextSimple {
3211 my ( $this, $text, $limit ) = @_;
3212 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
3213 if DEBUG;
3214
3215 # SMELL: need to avoid splitting within multi-byte characters
3216 # by encoding bytes as Perl UTF-8 characters.
3217 # This avoids splitting within a Unicode codepoint (or a UTF-16
3218 # surrogate pair, which is encoded as a single Perl UTF-8 character),
3219 # but we ideally need to avoid splitting closely related Unicode
3220 # codepoints.
3221 # Specifically, this means Unicode combining character sequences (e.g.
3222 # letters and accents)
3223 # Might be better to split on \b if possible.
3224
3225 $text =~
3226 s/^(.{$limit}.*?)($Foswiki::regex{mixedAlphaNumRegex}).*$/$1$2 \.\.\./s;
3227
3228 return $this->_makeSummaryTextSafe($text);
3229}
3230
3231sub _makeSummaryTextSafe {
3232 my ( $this, $text ) = @_;
3233
3234 my $session = $this->session();
3235 my $renderer = $session->renderer();
3236
3237 # We do not want the summary to contain any $variable that formatted
3238 # searches can interpret to anything (Item3489).
3239 # Especially new lines (Item2496)
3240 # To not waste performance we simply replace $ by $<nop>
3241 $text =~ s/\$/\$<nop>/g;
3242
3243 # Escape Interwiki links and other side effects introduced by
3244 # plugins later in the rendering pipeline (Item4748)
3245 $text =~ s/\:/<nop>\:/g;
3246 $text =~ s/\s+/ /g;
3247
3248 return $this->session->renderer->protectPlainText($text);
3249}
3250
3251=begin TML
3252
3253---++ ObjectMethod _summariseTextWithSearchContext( $text, $limit, $type, $searchOptions ) -> $tml
3254
3255Improves 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.
3256
3257The =\%searchOptions= hash may contain the following options:
3258 * =type= - search type: keyword, literal, query
3259 * =casesensitive= - false to ignore case (default true)
3260 * =wordboundaries= - if type is 'keyword'
3261 * =tokens= - array ref of search tokens
3262
3263=cut
3264
3265sub _summariseTextWithSearchContext {
3266 my ( $this, $text, $limit, $searchOptions ) = @_;
3267
3268 if ( !$searchOptions->{tokens} ) {
3269 return $this->_summariseTextSimple( $text, $limit );
3270 }
3271
3272 my $type = $searchOptions->{type} || '';
3273 if ( $type ne 'keyword' && $type ne 'literal' && $type ne '' ) {
3274 return $this->_summariseTextSimple( $text, $limit );
3275 }
3276
3277 my $caseSensitive = $searchOptions->{casesensitive} || '';
3278 my $wordBoundaries = $searchOptions->{wordboundaries} || '';
3279
3280 my @tokens = grep { !/^!.*$/ } @{ $searchOptions->{tokens} };
3281 my $keystrs = join( '|', @tokens );
3282
3283 if ( !$keystrs ) {
3284 return $this->_summariseTextSimple( $text, $limit );
3285 }
3286
3287 # we don't have a means currently to set the word window through a parameter
3288 # so we always use the default
3289 my $context = $SUMMARY_DEFAULT_CONTEXT;
3290
3291# break on words with search type 'word' (which is passed as type 'keyword' with $wordBoundaries as true
3292 my $wordBoundaryAnchor =
3293 ( $type eq 'keyword' && $wordBoundaries ) ? '\b' : '';
3294 $keystrs = $caseSensitive ? "($keystrs)" : "((?i:$keystrs))";
3295 my $termsPattern = $wordBoundaryAnchor . $keystrs . $wordBoundaryAnchor;
3296
3297# 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
3298 my $beforePattern = "(\\b.{0,$context}$wordBoundaryAnchor)";
3299 my $afterPattern = "($wordBoundaryAnchor.{0,$context}\\b)";
3300 my $searchPattern = $beforePattern . $termsPattern . $afterPattern;
3301
3302 my $summary = '';
3303 my $summaryLength = 0;
3304 while ( $summaryLength < $limit && $text =~ m/$searchPattern/gs ) {
3305 my $before = $1 || '';
3306 my $term = $2 || '';
3307 my $after = $3 || '';
3308
3309 $before = $this->_makeSummaryTextSafe($before);
3310 $term = $this->_makeSummaryTextSafe($term);
3311 $after = $this->_makeSummaryTextSafe($after);
3312
3313 $summaryLength += length "$before$term$after";
3314
3315 my $startLoc = $-[0];
3316
3317 # only show ellipsis when not at the start
3318 # and when we don't have any summary text yet
3319 if ( !$summary && $startLoc != 0 ) {
3320 $before = "$SUMMARY_ELLIPSIS $before";
3321 }
3322
3323 my $endLoc = $+[0] || $-[0];
3324 $after = "$after $SUMMARY_ELLIPSIS" if $endLoc != length $text;
3325
3326 $summary .= $before . CGI::em( {}, $term ) . $after . ' ';
3327 }
3328
3329 return $this->_summariseTextSimple( $text, $limit ) if !$summary;
3330
3331 return $summary;
3332}
3333
3334=begin TML
3335
3336---++ ObjectMethod summariseChanges( $orev, $nrev, $tml) -> $text
3337
3338Generate a (max 3 line) summary of the differences between the revs.
3339
3340 * =$orev= - older rev, if not defined will use ($nrev - 1)
3341 * =$nrev= - later rev, if not defined defaults to latest
3342 * =$tml= - if true will generate renderable TML (i.e. HTML with NOPs.
3343 If false will generate a summary suitable for use in plain text
3344 (mail, for example)
3345
3346If there is only one rev, a topic summary will be returned.
3347
3348If =$tml= is not set, all HTML will be removed.
3349
3350In non-tml, lines are truncated to 70 characters. Differences are shown using + and - to indicate added and removed text.
3351
3352=cut
3353
3354sub summariseChanges {
3355 my ( $this, $orev, $nrev, $tml ) = @_;
3356 my $summary = '';
3357 my $session = $this->session();
3358 my $renderer = $session->renderer();
3359
3360 ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
3361 if DEBUG;
3362 $nrev = $this->getLatestRev() unless $nrev;
3363
3364 ASSERT( $nrev =~ /^\s*\d+\s*/ ) if DEBUG; # looks like a number
3365
3366 $orev = $nrev - 1 unless defined($orev);
3367
3368 ASSERT( $orev =~ /^\s*\d+\s*/ ) if DEBUG; # looks like a number
3369 ASSERT( $orev >= 0 ) if DEBUG;
3370 ASSERT( $nrev >= $orev ) if DEBUG;
3371
3372 unless ( defined $this->{_loadedRev} && $this->{_loadedRev} eq $nrev ) {
3373 $this = $this->load($nrev);
3374 }
3375
3376 my $ntext = '';
3377 if ( $this->haveAccess('VIEW') ) {
3378
3379 # Only get the text if we have access to nrev
3380 $ntext = $this->text();
3381 }
3382
3383 return '' if ( $orev == $nrev ); # same rev, no differences
3384
3385 my $nstring = $this->stringify();
3386 $nstring =~ s/^%META:TOPICINFO{.*?}%//ms;
3387
3388 #print "SSSSSS nstring\n($nstring)\nSSSSSS\n\n";
3389
3390 $ntext = $renderer->TML2PlainText( $nstring, $this, 'showvar showmeta' );
3391
3392 #print "SSSSSS ntext\n($ntext)\nSSSSSS\n\n";
3393
3394 my $oldTopicObject =
3395 Foswiki::Meta->load( $session, $this->web, $this->topic, $orev );
3396 unless ( $oldTopicObject->haveAccess('VIEW') ) {
3397
3398 # No access to old rev, make a blank topic object
3399 $oldTopicObject =
3400 Foswiki::Meta->new( $session, $this->web, $this->topic, '' );
3401 }
3402
3403 my $ostring = $oldTopicObject->stringify();
3404 $ostring =~ s/^%META:TOPICINFO{.*?}%$//ms;
3405
3406 #print "SSSSSS ostring\n$ostring\nSSSSSS\n\n";
3407
3408 my $otext =
3409 $renderer->TML2PlainText( $ostring, $oldTopicObject, 'showvar showmeta' );
3410
3411 #print "SSSSSS otext\n($otext)\nSSSSSS\n\n";
3412
3413 require Foswiki::Merge;
3414 my $blocks = Foswiki::Merge::simpleMerge( $otext, $ntext, qr/[\r\n]+/ );
3415
3416 #foreach $b ( @$blocks ) {
3417 # print "BBBB\n($b)\nBBBB\n\n";
3418 # }
3419
3420 # sort through, keeping one line of context either side of a change
3421 my @revised;
3422 my $getnext = 0;
3423 my $prev = '';
3424 my $ellipsis = $tml ? $SUMMARY_ELLIPSIS : '...';
3425 my $trunc = $tml ? $SUMMARY_TMLTRUNC : $CHANGES_SUMMARY_PLAINTRUNC;
3426 while ( scalar @$blocks && scalar(@revised) < $CHANGES_SUMMARY_LINECOUNT ) {
3427 my $block = shift(@$blocks);
3428 next unless $block =~ /\S/;
3429 my $trim = length($block) > $trunc;
3430 $block =~ s/^(.{$trunc}).*$/$1/ if ($trim);
3431 if ( $block =~ m/^[-+]/ ) {
3432 if ($tml) {
3433 $block =~ s/^-(.*)$/CGI::del( {}, $1 )/se;
3434 $block =~ s/^\+(.*)$/CGI::ins( {}, $1 )/se;
3435 }
3436 elsif ( $session->inContext('rss') ) {
3437 $block =~ s/^-/REMOVED: /;
3438 $block =~ s/^\+/INSERTED: /;
3439 }
3440 push( @revised, $prev ) if $prev;
3441 $block .= $ellipsis if $trim;
3442 push( @revised, $block );
3443 $getnext = 1;
3444 $prev = '';
3445 }
3446 else {
3447 if ($getnext) {
3448 $block .= $ellipsis if $trim;
3449 push( @revised, $block );
3450 $getnext = 0;
3451 $prev = '';
3452 }
3453 else {
3454 $prev = $block;
3455 }
3456 }
3457 }
3458 if ($tml) {
3459 $summary = join( CGI::br(), @revised );
3460 }
3461 else {
3462 $summary = join( "\n", @revised );
3463 }
3464
3465 unless ($summary) {
3466 return $this->summariseText( '', $ntext );
3467 }
3468
3469 #print "SUMMARY\n===================\n($summary)\n============\n\n";
3470
3471 if ( !$tml ) {
3472 $summary = $renderer->protectPlainText($summary);
3473 }
3474 return $summary;
3475}
3476
3477=begin TML
3478
3479---++ ObjectMethod getEmbeddedStoreForm() -> $text
3480
3481Generate the embedded store form of the topic. The embedded store
3482form has meta-data values embedded using %META: lines. The text
3483stored in the meta is taken as the topic text.
3484
3485TODO: Soooo.... if we wanted to make a meta->setPreference('VARIABLE', 'Values...'); we would have to change this to
3486 1 see if that preference is set in the {_text} using the * Set syntax, in which case, replace that
3487 2 or let the META::PREF.. work as it does now..
3488
3489yay :/
3490
3491TODO: can we move this code into Foswiki::Serialise ?
3492
3493=cut
3494
3495
# spent 6.97ms (82µs+6.89) within Foswiki::Meta::getEmbeddedStoreForm which was called 2 times, avg 3.49ms/call: # 2 times (82µs+6.89ms) by Foswiki::Func::readTopicText at line 3456 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm, avg 3.49ms/call
sub getEmbeddedStoreForm {
349624µs my $this = shift;
3497
3498210µs27µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 7µs making 2 calls to Assert::ASSERTS_OFF, avg 3µs/call
3499 if DEBUG;
3500
3501243µs46.88ms return Foswiki::Serialise::serialise( $this->session(), $this, 'Embedded' );
# spent 6.87ms making 2 calls to Foswiki::Serialise::serialise, avg 3.43ms/call # spent 16µs making 2 calls to Foswiki::Meta::session, avg 8µs/call
3502}
3503
3504=begin TML
3505
3506---++ ObjectMethod setEmbeddedStoreForm( $text )
3507
3508Populate this object with embedded meta-data from $text. This method
3509is a utility provided for use with stores that store data embedded in
3510topic text. Only valid on topics.
3511
3512Note: line endings must be normalised to \n *before* calling this method.
3513
3514=cut
3515
3516
# spent 131ms (13.4+118) within Foswiki::Meta::setEmbeddedStoreForm which was called 70 times, avg 1.87ms/call: # 70 times (13.4ms+118ms) by Foswiki::Store::VC::Store::readTopic at line 110 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Store/VC/Store.pm, avg 1.87ms/call
sub setEmbeddedStoreForm {
351770351µs my ( $this, $text ) = @_;
3518
351970349µs70259µs ASSERT( $this->{_web} && $this->{_topic}, 'this is not a topic object' )
# spent 259µs making 70 calls to Assert::ASSERTS_OFF, avg 4µs/call
3520 if DEBUG;
3521
352270137µs my $format = $EMBEDDING_FORMAT_VERSION;
3523
3524 # head meta-data
3525702.17ms2041.15ms $text =~ s/^(%META:(TOPICINFO){(.*)}%\n)/
# spent 624µs making 70 calls to Foswiki::Meta::CORE:subst, avg 9µs/call # spent 531µs making 134 calls to Foswiki::Meta::CORE:substcont, avg 4µs/call
352667489µs6722.7ms $this->_readMETA($1, $2, $3)/e
# spent 22.7ms making 67 calls to Foswiki::Meta::_readMETA, avg 339µs/call
3527 ; #NO THIS CANNOT BE /g - TOPICINFO is _only_ valid as the first line!
352870446µs701.52ms my $ti = $this->get('TOPICINFO');
# spent 1.52ms making 70 calls to Foswiki::Meta::get, avg 22µs/call
352970195µs if ($ti) {
353067165µs $format = $ti->{format} || 0;
3531
3532 # Make sure we update the topic format for when we save
353367116µs $ti->{format} = $EMBEDDING_FORMAT_VERSION;
3534
3535 # Clean up SVN and other malformed rev nums. This can happen
3536 # when old code (e.g. old plugins) generated the meta.
353767484µs671.34ms $ti->{version} = Foswiki::Store::cleanUpRevID( $ti->{version} );
# spent 1.34ms making 67 calls to Foswiki::Store::cleanUpRevID, avg 20µs/call
353867153µs $ti->{rev} = $ti->{version}; # not used, maintained for compatibility
353967379µs35539µs $ti->{reprev} = Foswiki::Store::cleanUpRevID( $ti->{reprev} )
# spent 539µs making 35 calls to Foswiki::Store::cleanUpRevID, avg 15µs/call
3540 if defined $ti->{reprev};
3541 }
3542 else {
3543
3544 #defaults..
3545 }
3546
3547 # Other meta-data
354870103µs my $endMeta = 0;
3549701.09ms70373µs if ( $format !~ /^[\d.]+$/ || $format < 1.1 ) {
# spent 373µs making 70 calls to Foswiki::Meta::CORE:match, avg 5µs/call
3550 require Foswiki::Compatibility;
3551 if (
3552 $text =~ s/^%META:([^{]+){(.*)}%\n/
3553 Foswiki::Compatibility::readSymmetricallyEncodedMETA(
3554 $this, $1, $2 ); ''/gem
3555 )
3556 {
3557 $endMeta = 1;
3558 }
3559 }
3560 else {
3561705.03ms4712.63ms if (
# spent 1.92ms making 401 calls to Foswiki::Meta::CORE:substcont, avg 5µs/call # spent 709µs making 70 calls to Foswiki::Meta::CORE:subst, avg 10µs/call
3562 $text =~ s/^(%META:([^{]+){(.*)}%\n)/
35633421.28ms if ($2 ne 'TOPICINFO') {
3564 #TOPICINFO is only valid on the first line
35653421.86ms34285.1ms $this->_readMETA($1, $2, $3)
# spent 85.1ms making 342 calls to Foswiki::Meta::_readMETA, avg 249µs/call
3566 } else {
3567 $1
3568 }/gem
3569 )
3570 {
35715994µs $endMeta = 1;
3572 }
3573 }
3574
3575 # eat the extra newline put in to separate text from tail meta-data
357670621µs59301µs $text =~ s/\n$//s if $endMeta;
# spent 301µs making 59 calls to Foswiki::Meta::CORE:subst, avg 5µs/call
3577
3578 # If there is no meta data then convert from old format
357970564µs701.42ms if ( !$this->count('TOPICINFO') ) {
# spent 1.42ms making 70 calls to Foswiki::Meta::count, avg 20µs/call
3580
3581 # The T-word string must remain unchanged for the compatibility
3582377µs351µs if ( $text =~ /<!--TWikiAttachment-->/ ) {
# spent 51µs making 3 calls to Foswiki::Meta::CORE:match, avg 17µs/call
3583 require Foswiki::Compatibility;
3584 $text = Foswiki::Compatibility::migrateToFileAttachmentMacro(
3585 $this->{_session}, $this, $text );
3586 }
3587
3588 # The T-word string must remain unchanged for the compatibility
3589387µs366µs if ( $text =~ /<!--TWikiCat-->/ ) {
# spent 66µs making 3 calls to Foswiki::Meta::CORE:match, avg 22µs/call
3590 require Foswiki::Compatibility;
3591 $text =
3592 Foswiki::Compatibility::upgradeCategoryTable( $this->{_session},
3593 $this->{_web}, $this->{_topic}, $this, $text );
3594 }
3595 }
3596 elsif ( $format eq '1.0beta' ) {
3597 require Foswiki::Compatibility;
3598
3599 # This format used live at DrKW for a few months
3600 # The T-word string must remain unchanged for the compatibility
3601 if ( $text =~ /<!--TWikiCat-->/ ) {
3602 $text =
3603 Foswiki::Compatibility::upgradeCategoryTable( $this->{_session},
3604 $this->{_web}, $this->{_topic}, $this, $text );
3605 }
3606 Foswiki::Compatibility::upgradeFrom1v0beta( $this->{_session}, $this );
3607 if ( $this->count('TOPICMOVED') ) {
3608 my $moved = $this->get('TOPICMOVED');
3609 $this->put( 'TOPICMOVED', $moved );
3610 }
3611 }
3612
361370637µs70283µs if ( $format !~ /^[\d.]+$/ || $format < 1.1 ) {
# spent 283µs making 70 calls to Foswiki::Meta::CORE:match, avg 4µs/call
3614
3615 # compatibility; topics version 1.0 and earlier equivalenced tab
3616 # with three spaces. Respect that.
3617 $text =~ s/\t/ /g;
3618 }
3619
362070793µs $this->{_text} = $text;
3621}
3622
3623=begin TML
3624
3625---++ ObjectMethod isValidEmbedding($macro, \%args) -> $boolean
3626
3627Test that the arguments defined in =\%args= are sufficient to satisfy the
3628requirements of the embeddable meta-data given by =$macro=. For example,
3629=isValidEmbedding('FILEATTACHMENT', $args)= will only succeed if $args contains
3630at least =name=, =date=, =user= and =attr= fields. Note that extra fields are
3631simply ignored (unless they are explicitly excluded).
3632
3633If the macro is not registered for validation, then it will be ignored.
3634
3635If the embedding is not valid, then $Foswiki::Meta::reason is set with a
3636message explaining why.
3637
3638=cut
3639
3640
# spent 18.4ms within Foswiki::Meta::isValidEmbedding which was called 409 times, avg 45µs/call: # 409 times (18.4ms+0s) by Foswiki::Meta::_readMETA at line 3682, avg 45µs/call
sub isValidEmbedding {
3641409793µs my ( $this, $macro, $args ) = @_;
3642
3643409867µs my $validate = $VALIDATE{$macro};
3644409526µs return 1 unless $validate; # not validated
3645
3646409686µs if ( defined $validate->{function} ) {
3647 unless ( &{ $validate->{function} }( $macro, $args ) ) {
3648 $reason = "\%META:$macro validation failed";
3649 return 0;
3650 }
3651
3652 # Fall through to check other constraints
3653 }
3654
3655409538µs my %allowed;
36564091.07ms if ( defined $validate->{require} ) {
365711543.28ms map { $allowed{$_} = 1 } @{ $validate->{require} };
36585941.71ms foreach my $p ( @{ $validate->{require} } ) {
36595601.69ms if ( !defined $args->{$p} ) {
3660 $reason = "$p was missing from \%META:$macro";
3661 return 0;
3662 }
3663 }
3664 }
3665
3666409857µs if ( defined $validate->{allow} ) {
36678052.68ms map { $allowed{$_} = 1 } @{ $validate->{allow} };
3668112593µs foreach my $arg ( keys %$args ) {
36693701.05ms if ( !$allowed{$arg} ) {
3670 $reason = "$arg was present in \%META:$macro";
3671 return 0;
3672 }
3673 }
3674 }
3675
36764092.63ms return 1;
3677}
3678
3679
# spent 108ms (12.7+95.2) within Foswiki::Meta::_readMETA which was called 409 times, avg 264µs/call: # 342 times (10.2ms+74.9ms) by Foswiki::Meta::setEmbeddedStoreForm at line 3565, avg 249µs/call # 67 times (2.50ms+20.2ms) by Foswiki::Meta::setEmbeddedStoreForm at line 3526, avg 339µs/call
sub _readMETA {
36804091.50ms my ( $this, $expr, $type, $args ) = @_;
36814092.02ms40952.9ms my $keys = _readKeyValues($args);
# spent 52.9ms making 409 calls to Foswiki::Meta::_readKeyValues, avg 129µs/call
36824092.27ms40918.4ms if ( $this->isValidEmbedding( $type, $keys ) ) {
# spent 18.4ms making 409 calls to Foswiki::Meta::isValidEmbedding, avg 45µs/call
36834091.19ms if ( defined( $keys->{name} ) ) {
3684
3685 # save it keyed if it has a name
36863391.89ms33919.9ms $this->putKeyed( $type, $keys );
# spent 19.9ms making 339 calls to Foswiki::Meta::putKeyed, avg 59µs/call
3687 }
3688 else {
368970509µs703.94ms $this->put( $type, $keys );
# spent 3.94ms making 70 calls to Foswiki::Meta::put, avg 56µs/call
3690 }
36914092.21ms return '';
3692 }
3693 else {
3694 return $expr;
3695 }
3696}
3697
3698# STATIC Build a hash by parsing name=value comma separated pairs
3699# SMELL: duplication of Foswiki::Attrs, using a different
3700# system of escapes :-(
3701
# spent 52.9ms (25.1+27.8) within Foswiki::Meta::_readKeyValues which was called 409 times, avg 129µs/call: # 409 times (25.1ms+27.8ms) by Foswiki::Meta::_readMETA at line 3681, avg 129µs/call
sub _readKeyValues {
3702409806µs my ($args) = @_;
3703409522µs my %res;
3704
3705 # Format of data is name='value' name1='value1' [...]
370640918.9ms23388.47ms $args =~ s/\s*([^=]+)="([^"]*)"/
# spent 6.29ms making 1929 calls to Foswiki::Meta::CORE:substcont, avg 3µs/call # spent 2.18ms making 409 calls to Foswiki::Meta::CORE:subst, avg 5µs/call
3707152210.3ms152219.3ms $res{$1} = dataDecode( $2 ), ''/ge;
# spent 19.3ms making 1522 calls to Foswiki::Meta::dataDecode, avg 13µs/call
3708
37094092.21ms return \%res;
3710}
3711
3712=begin TML
3713
3714---++ StaticMethod dataDecode( $encoded ) -> $decoded
3715
3716Decode escapes in a string that was encoded using dataEncode
3717
3718The encoding has to be exported because Foswiki (and plugins) use
3719encoded field data in other places e.g. RDiff, mainly as a shorthand
3720for the properly parsed meta object. Some day we may be able to
3721eliminate that....
3722
3723=cut
3724
3725
# spent 19.3ms (16.7+2.64) within Foswiki::Meta::dataDecode which was called 1522 times, avg 13µs/call: # 1522 times (16.7ms+2.64ms) by Foswiki::Meta::_readKeyValues at line 3707, avg 13µs/call
sub dataDecode {
372615223.39ms my $datum = shift;
3727
372815269.10ms15282.64ms $datum =~ s/%([\da-f]{2})/chr(hex($1))/gei;
# spent 2.63ms making 1522 calls to Foswiki::Meta::CORE:subst, avg 2µs/call # spent 17µs making 6 calls to Foswiki::Meta::CORE:substcont, avg 3µs/call
372915228.02ms return $datum;
3730}
3731
3732133µs1;
3733__END__
 
# spent 773µs within Foswiki::Meta::CORE:match which was called 146 times, avg 5µs/call: # 70 times (373µs+0s) by Foswiki::Meta::setEmbeddedStoreForm at line 3549, avg 5µs/call # 70 times (283µs+0s) by Foswiki::Meta::setEmbeddedStoreForm at line 3613, avg 4µs/call # 3 times (66µs+0s) by Foswiki::Meta::setEmbeddedStoreForm at line 3589, avg 22µs/call # 3 times (51µs+0s) by Foswiki::Meta::setEmbeddedStoreForm at line 3582, avg 17µs/call
sub Foswiki::Meta::CORE:match; # opcode
# spent 6.44ms within Foswiki::Meta::CORE:subst which was called 2130 times, avg 3µs/call: # 1522 times (2.63ms+0s) by Foswiki::Meta::dataDecode at line 3728, avg 2µs/call # 409 times (2.18ms+0s) by Foswiki::Meta::_readKeyValues at line 3706, avg 5µs/call # 70 times (709µs+0s) by Foswiki::Meta::setEmbeddedStoreForm at line 3561, avg 10µs/call # 70 times (624µs+0s) by Foswiki::Meta::setEmbeddedStoreForm at line 3525, avg 9µs/call # 59 times (301µs+0s) by Foswiki::Meta::setEmbeddedStoreForm at line 3576, avg 5µs/call
sub Foswiki::Meta::CORE:subst; # opcode
# spent 8.75ms within Foswiki::Meta::CORE:substcont which was called 2470 times, avg 4µs/call: # 1929 times (6.29ms+0s) by Foswiki::Meta::_readKeyValues at line 3706, avg 3µs/call # 401 times (1.92ms+0s) by Foswiki::Meta::setEmbeddedStoreForm at line 3561, avg 5µs/call # 134 times (531µs+0s) by Foswiki::Meta::setEmbeddedStoreForm at line 3525, avg 4µs/call # 6 times (17µs+0s) by Foswiki::Meta::dataDecode at line 3728, avg 3µs/call
sub Foswiki::Meta::CORE:substcont; # opcode