Commit graph

286 commits

Author SHA1 Message Date
sbailey 8cb34b6a4c Add an html/parsoid section for "Error conditions on non-visible content"
Also, FIXMEs for a follow up to 4438a72 that's exposed by this test.
Nested refs in references aren't getting marked up for the
"cite_error_ref_no_text" errors, where applicable.

Change-Id: Ie6e461571402a96e47d3df26585d9a40f1038891
2020-06-25 19:04:57 -04:00
Subramanya Sastry d69aea2feb Extension API: Use generic 'context' option instead of 'inlineContext'
* This lets us expand the range of available contexts in the future
  without needing API changes.

* This patch only touches extension and extension API code. Parsoid
  internal code can be changed independently.

Change-Id: I51d4c2120a31efb6dbb409926f8f8dad61f4dcc3
2020-06-25 17:34:01 -04:00
sbailey 4438a72297 Adding error handling for cite refs with name but no content
* Detects grouped and named refs that fail to define content.

* Uses group and name ref list tracking info to back patch
  'mw:Error' and i18n error key string into the data-mw
  section of all instances of named refs that all fail to
  define content.

* The failures for test References: 7b is because selser is
  arguable smarter than wt2wt. The newline before the references
  list has been randomly deleted but selser manages to restore it
  from source. wt2wt doesn't put the references tag on a line by
  itself, even though it asks for block format, because it isn't
  a new list - (these comments are from Arlo's review)

* Added test: "References: 7b. Multiple references tags some with
  errors..." to ensure that refs with and without content errors
  grouped and named do not cross references section boundaries.

Bug: T51538
Change-Id: I884fc337165506c5abbef18bcd5a5fca015786d2
2020-06-25 14:58:08 -04:00
Arlo Breault e911944c5f Clarify when content is missing in cite
Change-Id: Icbf195059ddae410944ecdcaf02cbfff7f962bf2
2020-06-23 10:54:06 -04:00
Subramanya Sastry faaf81140d Fix extensions to use Ext\DOMUtils instead of Utils\DOMUtils
* Added a couple missing helpers

Change-Id: Ia789e6f8fe6e53d187bd631003234930b3cee3f0
2020-06-12 16:43:24 -05:00
Arlo Breault 1446ca248c Whitespace only is considered no content
Follow up to 0cac84e

Bug: T51538
Change-Id: Id84cc2ff9d734f94b7788a9bc0cea059522c9a0c
2020-06-09 15:17:39 -04:00
C. Scott Ananian 98d68bfa6e Use wikimedia\object-factory for extension objects; hook up ExtensionRegistry
Mediawiki prefers to use an object factory pattern when creating objects.
Use ObjectFactory consistently when creating objects specified using the
extension API.  Thanks to the 'allowClassName' option to
ObjectFactory::getObjectFromSpec(), this is mostly consistent with
previous practice.  Note that the string class name is short for:
   [ 'class' => Foo::class ]
and so we've chosen to rename the 'class' property in the extension tag
configuration so that we have (in long form):
   [ 'name' => 'Cite', 'handler' => [ 'class' => Cite::class ] ]
instead of nesting two keys named 'class' in a row.  (And besides, the
content isn't really a 'class' any more, it's an "object factory
 specification".)

SiteConfig::registerExtensionModule() can now take *either* an object
factory specification for an ExtensionModule object (including a bare
class-string) *or* the contents of the configuration array that
would be returned by ExtensionModule::getConfig(), in which case it
creates an anonymous ExtensionModule object for you.  It's expected
that the latter will be preferred in extension.json, but we use the
former for our internal extension implementations at the moment.

Finally, call SiteConfig::registerExtensionModule() on the results
of ExtensionRegistery::getInstance()->getAttribute('ParsoidModules')
when running in integrated mode.  This allows you to register your
extension with a clause such as the following in your extension.json:

(simple case, naming a class which implements ExtensionModule)
{
  "name": "JsonExtension",
  "manifest_version": 2,
  ...
  "ParsoidModules": [ "Wikimedia\\Parsoid\\Ext\\JSON" ]
}

(complex case, putting the configuration array into extension.json)
{
  "name": "Cite",
  "manifest_version": 2,
  ...
  "ParsoidModules": [
    {
      "name": "Cite",
      "domProcessors": [
	"Wikimedia\\Parsoid\\Ext\\Cite\\RefProcessor",
      ],
      "tags": [
	{
	  "name": "ref",
	  "handler": "Wikimedia\\Parsoid\\Ext\\Cite\\Ref",
	  "options": {
	    "wt2html": { "sealFragment": true }
	  },
	},
	{
	  "name": "references",
	  "handler": "Wikimedia\\Parsoid\\Ext\\Cite\\References",
	  "options": {
	    "html2wt": { "format": "block" }
	  },
	}
      ],
      "styles": [
	"ext.cite.style",
	"ext.cite.styles"
      ]
    }
  ]
}

The syntax above, with `ParsoidModules` as a top-level attribute, requires
I6c74938883376ec17f3790678b435585083a440f in core.  However, with or without
that patch, the following also works:
{
  ...
  "attributes": {
    "Parsoid": {
      "Modules": [ ... ]
    }
  }
}

Bug: T133320
Change-Id: I20f641a1ff032a6da3549b01dfaf8f4cf1eb5071
2020-05-29 12:18:36 -04:00
sbailey 0cac84e6b2 Fix Cite extension <ref> no name and no content error handling
* The html generated matches the spec:
  https://www.mediawiki.org/wiki/Specs/HTML/2.1.0#Error_handling
  data-mw errors format.

* The html generated also matches the spec:
  https://phabricator.wikimedia.org/T251842

Bug: T51538
Change-Id: I7b3a3ddd72abfab22b4565f7282f7ba95b246301
2020-05-28 10:53:21 -04:00
C. Scott Ananian 46f749b92f Use DOMUtils::hasTypeOf/matchTypeOf/addTypeOf consistently
The `typeof` attribute is a space-separated list.  Use consistent methods
to test for membership in that list.

Try to set a good example by using ::addTypeOf() in general to set the
value of the 'typeof' attribute, except when we are transferring values
from one node to another.

Move the *TypeOf method to DOMUtils from DOMDataUtils, eliminating some
duplication.  Create Wikimedia\Parsoid\Ext\DOMUtils so that extensions
can use these methods as well.

Change-Id: Ib2ef827ef1cf5e31a1ef0a5034cdd3d9a0212bdb
2020-05-26 23:57:18 -04:00
Arlo Breault a174963f3e Stop excluding MediaWiki.Commenting.FunctionComment.MissingDocumentationPrivate
Since we aren't excluding the other two, MissingDocumentationPublic and
MissingDocumentationProtected.

The stubs could be useful if we ever expanded on what these functions do
and doxygen probably gets the information from here instead of the type
hints?

Change-Id: Ie18c4f00ceca8f06b9c0f0a3359cb4077892f97d
2020-05-26 14:58:03 -04:00
C. Scott Ananian 51c211047a All extension DOM processors should extend Ext\DOMProcessor
Change-Id: Ide9700747b3ecea9da59911c6eb342569be4c9b8
2020-05-03 21:15:41 +00:00
C. Scott Ananian b4aefae357 Add extension registration mechanism to SiteConfig
This will be called by the ExtensionRegistry in core for extensions
that are Parsoid-compatible.  The set of registered extensions is part
of the SiteConfig, which bundles all the configuration for a particular
Parsoid instance.

In addition, renamed some classes to make things clearer:
`ExtensionModule` is the thing which is registered; it bundles a
number of `ExtensionTagHandler`s, `ContentModelHandler`s, and DOM
processors (which don't have a proper interface yet).  There are a set
of core handlers, which include wikitext, JSON, and a few extension
tags (<pre>, <nowiki>, <gallery>).

Change-Id: Iadbeb378bacb09264a4b1d3ee430a914eec23e48
2020-05-03 15:43:11 -05:00
Arlo Breault bb5f2fb93d Move DOMDataUtils::addAttributes to DOMUtils
Bug: T250888
Change-Id: Ia12bbe21c21b188a8b1fdc4b8cee0aa153d2b993
2020-04-28 13:30:56 +00:00
Subramanya Sastry 5626d6ca62 ParsoidExtensionAPI: Add domToWikitext method + fix Cite to use it
* We only had a htmlToWikitext API method whereas we have been
  trying to stay in DOM land all along. With this change, extensions
  can use the intuitive domToWikitext method when they are dealing
  with DOM nodes.

* Renamed WTS's serializeHTML method to htmlToWikitext and added
  a domToWikitext method there as well which ParsoidExtensionAPI uses.

* Turns out that <ref>s were converting DOM to HTML and then using
  the htmlToWikitext method. I switched it use the domToWikitext
  method. However, turns out WTS requires a <body> element for its
  top-level method!

  For now, while we figure out if that can be changed to be more
  lenient, added an internal DOM -> HTML conversion in the
  domToWikitext method. When we fix WTS, this DOM -> HTML -> DOM
  roundtrip can be eliminated.

Bug: T242746
Change-Id: I340d5a363e0d1b8ed6d0ffb0234315e6d9523a76
2020-04-17 20:58:53 +00:00
Subramanya Sastry 18462e0458 Extension API: Adopt somethingToSomethingElse naming wherever possible
* Gergo's sensible recommendation to make the API more readable and
  easier to use.

Bug: T242746
Change-Id: I6ed1d4bb552a15b39552f24cd425dd4d63cce847
2020-04-17 20:58:50 +00:00
Subramanya Sastry d5c4583649 ParsoidExtensionAPI: Cleanup the toHTML / innerHTML mess
This is cleaner and less prone to subtle errors since it forces
extension developers to explicitly choose the more performant version.

Bug: T242746
Change-Id: Ia25bc3ae261b43dba97d369940065254faacdd80
2020-04-03 18:48:01 +00:00
Subramanya Sastry 6db523d642 Extension Config Options: Reorg options for ease of use
Instead of 'fragmentOptions' and 'html2wt' for extension tags,
embed them as 'wt2html' and 'html2wt' components of an 'options'
property.

Bug: T242746
Change-Id: I4cf32a70ec76a415a98b68eef548206f8b917168
2020-04-01 23:54:18 +00:00
Subramanya Sastry c60c472366 Minor: Rename pipelineOpts to parseOpts in ParsoidExtensionAPI
Bug: T242746
Change-Id: I244e21b8222547aed9ba5bf902a46dfd114823f1
2020-04-01 23:54:16 +00:00
Subramanya Sastry 04e9292576 Provide utility classes for extensions
* Added DOMDataUtils, WTUtils, and Util for use by extension
  developers. These classes might acquire more functionality in the
  future based on usage and need.

* Added PHPUtils for a single helper to work around a GC bug in PHP.
  Once we move on to a newer version of PHP where this is fixed, we
  can get rid of this class and helper.

* These classes proxy the various helpers used by currently ported
  extensions. For reasons of coherency, the set of helpers in these
  classes are a superset of what the extensions use.

* Updated references to the other helpers to use these classes

* Since DOMUtils or DOMCompat are not Parsoid-centric, it feels safe
  to provide extensions direct access to those utils classes. We could
  consider moving DOMUtils to the Core/Utils namespace if appropriate.

* In one case, I replaced the escapeNowikiTags helper that the Nowiki
  "extension" used with an inlined preg_replace.

* TODO: Add unit tests to ensure these utils don't break!

Bug: T242746
Change-Id: I9e733f4ddd6fca8ce13c2957a7d0065d80f7ae9a
2020-03-25 22:17:13 +00:00
Subramanya Sastry 98da9ba908 Extensions: Remove inPHPBlock wt2html pipeline option
* The functionality looks effectively identical to inlineContext
  and everywhere inPHPBlock was inspected, inlineContext was also
  being inspected.

* Cite's use of this flag is a hack to get desired bacward compatible
  behavior but that is a hack no matter what we call the flag.

Change-Id: I3c62590b9bfda224897bb85b18d96c072f3d74ef
2020-03-25 11:11:09 -05:00
Subramanya Sastry 0cc3ca1b98 Move DomSourceRange to Core; ParsoidExtensionApi to Ext
* At this point, DSR is a first-class Parsoid concept and
  extensions will need to use this as well. So, make it part
  of the Core/ namespace to capture high-level concepts that
  might be used outside Parsoid itself.

* Move ParsoidExtensionApi to the Ext directory since that is
  where it best belongs.

Change-Id: If824c4af9e2f8d658f1cb726cbd837222b60790d
2020-03-16 15:52:08 +00:00
Subramanya Sastry 866bc09353 ParsoidExtensionAPI: Update docs
Change-Id: Id1bdf28254cda3ff32cd8ecab6eea8adfce31144
2020-03-06 19:07:23 -05:00
Subramanya Sastry 34b7080ebf ParsoidExtensionAPI: Add additional API methods
* Added API method to let content-model extensions to add metadata
  to <head>.
* The title API methods seem legitimate
* But, the newAboutId helper is suspect -- currently only needed
  by Cite. Explore if we can eliminate the need for this helper.
* This eliminates a few more Env use sites from extensions.

Bug: T242746
Change-Id: I0e982d4be173f7d49df19467fbf49c11d428e650
2020-03-06 19:07:17 -05:00
Subramanya Sastry 25bd654ce1 Cite: Eliminate knowledge of DOM state from a few more call sites
* Cite (or other extensions) don't need to explicitly load/store
  data attributes from html attributes to/from the data bag held
  separately from the DOM.

Bug: T242746
Change-Id: I4a52be2b06ccfe53d0cf81987af12a1d139fef4c
2020-03-06 22:01:40 +00:00
Subramanya Sastry 5397598842 Provide extensions SiteConfig & PageConfig access via ParsoidExtensionAPI
* Presumably, extensions would benefit from having access to the
  wiki config via SiteConfig.

* Yet to figure out if extensions need access to the page config.

* But, with this change, extensions don't need $env when all they
  need is access to the wiki and page config.

Bug: T242746
Change-Id: I88736f882f185ee9376b73f7e4bb0b2bd318bb1a
2020-03-05 19:30:38 -05:00
Subramanya Sastry aebd6bcdcd Save fragments without storing data-* attribs onto the node
* This seems to work and also will make the job of keeping extensions
  free of DOM state easier.

  Arlo clarifies that this wasn't necessary since f7594328 and could
  have been cleaned up there.

Change-Id: I96edaa5b2743f1ce0d8596acfdc59035491541cb
2020-03-02 23:22:47 +00:00
Subramanya Sastry 2f9f5e25ef Pass $extApi, not $env to extension callbacks
* $env was unused in extension DOM post processors. So get rid of
  that since we are already in the process of removing $env access
  to extensions.

* html2wtPreProcessor is currently unimplemented but there is WIP
  code in Parsoid/JS that can be revived at a later point. No need
  to pass $env here as well.

* In both cases, pass $extApi so they can access any necessary
  helpers or state provided by that API object.

Bug: T242746
Change-Id: I1d1544af817d03e01a569e6aeaeed0d6c3058fc0
2020-03-02 19:21:07 +00:00
Subramanya Sastry 14d9ed27f0 Remove direct access to Sanitizer from extension code
* Proxy all accesses to the santiizer via appropriately named methods
  in the ParsoidExtensionApi interface

Bug: T242746
Change-Id: I9d3d98639bb98b4abe404139786517591323d61d
2020-02-20 23:23:22 -06:00
Subramanya Sastry d0a9c42c98 Cite: Remove more Parsoid internals knowledge
* Remove use of $env from ReferencesData and RefGroup by
  providing high-level helpers in ParsoidExtensionAPI.

  - Given a fragment id, provide helpers to fetch fragment DOM
    or fragment HTML
  - Fetch the URI for the current page (being parsed)

* There is still a lot of subtle knowledge Cite has about
  how data-parsoid and data-mw attributes are held off to the
  side in a bag and all the pp* and load/store manipulation
  of those attributes. It would be an interesting exercise
  to purge this implementation of those notions OR figure out
  high-level concepts that we document as being part of Parsoid
  reality that we'll forever support.

Bug: T242746
Change-Id: I29ff154f2f17123b9756dfd2f3b422f0b30222b1
2020-02-11 19:47:28 +00:00
Subramanya Sastry 1f87104378 Simplify TokenUtils::kvToHash
* Get rid of unused args and simplifyy method
* In preparation for more cleanup of extension code

Change-Id: I9bdce2e0c9254405d4c3ed61926b54a3997a0c22
2020-02-11 15:50:58 +00:00
Subramanya Sastry 70e38c1ae4 Use extension config option for html2wt formatting of extension tags
Bug: T242746
Change-Id: If96056d9bc75afa9390c2f8aab0da5eab60cc537
2020-02-07 18:03:56 +00:00
Subramanya Sastry 5e256b48aa Start untangling Parsoid internals from extensions
* In this patch, toDOM, fromDOM, and DOM postprocessor extension
  methods all get a ParsoidExtensionAPI object. These API objects
  are constructed at the appropriate times in the wt2html and html2wt
  pipelines.

* Got rid of direct references to SerializerState from fromDOM
  methods in extensions.

* Exposed generic serialization and wikitext escaping methods
  in ParsoidExtensionAPI for extensions to leverage. The implementation
  of these methods is partial and only supports current usage
  of extensions in Parsoid's repo. This will need to be fully
  fleshed out going forward.

* Stopped exposing wt2html options in toto and provided more specific
  convenience methods.

* Reduced direct access to the Env object in a few more places.

* Cite has code to inspect embedded HTML in data attributes of a node.
  Moved this code out of Cite into ParsoidExtensionAPI which reduces
  knowledge that extensions need. Unlike the other cleanups, this one
  is more of a convenience method since this code only requires
  knowledge of a publicly published spec. But, nevertheless an useful
  cleanup since it simplifies Cite's complexity just a bit.

* More followup work is needed.
  - before/after methods should be eliminated in favour of a config flag
    that implements the inline/block layout option. Once this is done,
    extensions will no longer need direct access to the SerializerState
    internal object.
  - Env exposure should be reduced.
  - Provide access to Sanitizer via ParsoidExtensionAPI instead of
    needing extensions to directly import it.
  - It should be possible to eliminate the need for extensions to know
    about DSR / DSR-shifting and do it automatically via some high-level
    conceptual flag.
  - It might also be possible to infer source offsets directly via args
    instead of passing that explicitly.
  - Should we provide a convenience helper class with access to all the
    src/Utils/* methods?

Bug: T242746
Change-Id: I7ffb5aa52a84854a9d363a0e8f1ce650241f1c41
2020-02-06 20:55:27 -05:00
C. Scott Ananian 5d200e0bf0 Move all code from Parsoid to Wikimedia\Parsoid namespace
This matches core conventions.

Bug: T240054
Change-Id: I5feb8a6b41503accd01a740195256e9092609272
2020-02-03 21:34:49 +00:00
Subramanya Sastry 787e6b1cfe No need to explicitly pass 'inTemplate' flag from extension code
* ParsoidExtensionAPI has this info already.

Bug: T242746
Change-Id: I60c393716f31d6f288c54910399bef6e4a42f3dc
2020-01-22 12:56:43 +05:30
Arlo Breault cff011ff0a Serialize reference tags by themselves on a line
Bug: T242513
Change-Id: I0744eb27e357e63667da86347d3653f063789fca
2020-01-16 13:40:36 -05:00
Subramanya Sastry ddbca68066 Rename DOM handling methods toDOM/fromDOM to reflect reality
* For extensions, the fromHTML --> fromDOM renaming mirrors the
  toDOM extension method.

Change-Id: Ic361bd0b5a8849c3033f55cfcae6e1cb36f68a10
2020-01-16 20:25:36 +05:30
Subramanya Sastry a8a6951f9c Bump Parsoid/JS version to 0.11.0 for deb package release
Change-Id: I2f5df42fe7d8275f0a3d4d77cd4cd410f76e9974
2020-01-11 13:43:13 +05:30
Subramanya Sastry 1ec3fdbe33 Minor: Rename a function arg
Change-Id: I689eef42414749c2cf47be55268b5bb2575d8078
2019-11-22 20:54:03 +00:00
Subramanya Sastry ad229c1ac0 One more fix for T235656
It is about time to implement a generic fix for T214994 which will
capture the Cite-specific scenario we are tackling here in bits
and pieces. We have fragments of a T214994 solution spread in
Utils/ and here while tackling shiftDSR and convertOffsets
for Parsoid/PHP. So, someone needs to take a look at all these
pieces and implement the necessary abstraction.

As part of that, maybe we also need to revisit our embedded HTML
data-* representation so there is a uniform way to inspect these
attributes rather than every semantic element (template, extensions,
lang variants, media) choosing their own custom embedding scheme
which makes discoverability harder and error-prone.

Change-Id: Ifd19d2d8d20dbd0dda0fa1cc338a07afb37c4213
2019-11-06 14:03:29 -06:00
Arlo Breault 22832c1d41 Bump mediawiki-phan-config to 0.8.0
This managed to catch a few bugs where there is likely coverage gaps.

The regenerated plugin list again comments out a few that generated too
much noise but may be worthwhile follow ups.

Change-Id: I746abd0f1406b4b7ffa497afdad3939fe437c25d
2019-11-05 15:31:36 -05:00
C. Scott Ananian d55a8db4e4 Clean up typeOf matching in Cite extension
Change-Id: I88388a2120cc1bd79ef7ec5de3c40e93db22844f
2019-11-04 20:08:33 +00:00
Subramanya Sastry 965203b301 Process <ref>s found in nodes with mw:ExpandedAttrs typeof
* This is an instance of a bigger issue that we need to look closely
  when we are integrating Parsoid with core parser.

Bug: T235656
Change-Id: I3d652727293461c7968e83be8994ba0572bae8e4
2019-10-29 21:52:09 +00:00
Subramanya Sastry 79183b596d Cite wt->html: Match html->wt and make datamw->body an object not an array
* Though this doesn't immediately affect anything, it just makes
  usage a bit more consistent.

* A followup patch that fixes gaps in shiftDSR code will now be
  able to reference the html property as dmw->body->html to match
  html2wt usage.

Change-Id: I9dfcd9d40205f6e64e139bf3f75a322915af3232
2019-10-15 19:43:11 -05:00
Subramanya Sastry 2be3ab72c6 References.php: Use strlen not mb_strlen to count page length
* When a page is missing an explicit <references /> tag, we insert
  an implicit <references /> tag and assign it a zero-width DSR with
  a starting offset equal to the length of the page. However, now that
  we have byte offsets, that should have been strlen, not mb_strlen.

  This was causing incorrect DSR assignment on this implicit tag
  and causing trailing newline selser diffs on these pages.

* Debugged on this reduced test case: "* a – b <ref>x</ref>\n\nc\n\n"
  and comparing selser trace and then DSR offsets on the DOMs.

Change-Id: I8aebf307197935259df78251fb4a26c593f29603
2019-10-03 23:14:33 -05:00
Subramanya Sastry 167a28bbea Use PHPUtils::lastItem() over end() in more places
Tim Starling has indicated in couple different places that end(..)
is not preferred and he had implemented a private version of lastItem
in the PEG grammar code whereas PHPUtils::lastItem was recommending
use of end(..).

In this patch, I moved the implementation from the grammar to PHPUtils
and replaced end(..) with PHPUtils::lastItem in a number of places in
the codebase. We should discuss whether we want to use this helper
everywhere.

Resolved a couple of PORT-FIXMEs in the bargain.

Change-Id: I837f2a98003df8ab7dbdf9af045e17bdd6e27799
2019-10-03 03:41:39 +00:00
Arlo Breault 6d0c6201dd Resolve some PORT-FIXMEs around Selser construction
And rename Selser -> SelserData

Change-Id: Ia6a23f4194d4c05b7269498bfbbd31e236c86ce6
2019-09-19 17:53:57 -04:00
Subramanya Sastry 23b666ad14 Unconditionally add D modifier to regexps ending in $ check
* We could potentially also exclude regexps for node name checks
* A few additions looks like could potentially have caused subtle
  failures in edge cases.
* Unrelated changes: Used # regexp terminator in a number of regexps
  to eliminate escaping of / character.

Bug: T231980
Change-Id: Ie2451349684c248d93e064e3e7009d0d2d60acf3
2019-09-09 21:43:58 +00:00
Arlo Breault e6204a1561 Test against ref name length instead of coercing to bool
Since "0" is falsy in php.

Couple tests now pass.

Change-Id: I9b62b9f78680de6e1d5c31723af7212a58a535f3
2019-08-14 18:59:28 -04:00
Arlo Breault dc7d19a1a8 Avoid normalizing fragment being passed to newFromText
Matches what we're doing on the JS side.

Change-Id: I93a0770b84e496ddf3290a36fa6b8073919ed183
2019-08-14 22:23:53 +00:00
Arlo Breault b202964b5a Fix dropping nested refs
The extension handler expects a `null` for this, `false` is an
indication not to use the toDOM handler.

php bin/parserTests.php tests/citeParserTests.txt --filter "CircularRef" --wt2html

Change-Id: I849a9aca1133f8a793c9d77e05f192a6af5d78f9
2019-08-09 16:48:03 -04:00