Compare commits
	
		
			1 Commits
		
	
	
		
			actor_outb
			...
			apfeed
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					27d62f1135 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,2 +0,0 @@
 | 
			
		||||
/vendor/
 | 
			
		||||
composer.lock
 | 
			
		||||
							
								
								
									
										911
									
								
								ActivityPubPlugin.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										911
									
								
								ActivityPubPlugin.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,93 +0,0 @@
 | 
			
		||||
# Contributing
 | 
			
		||||
 | 
			
		||||
When contributing to this repository, please first discuss the change you wish to make via issue,
 | 
			
		||||
email, or any other method with the owners of this repository before making a change.
 | 
			
		||||
 | 
			
		||||
Please note we have a code of conduct, please follow it in all your interactions with the project.
 | 
			
		||||
 | 
			
		||||
# Coding Style
 | 
			
		||||
- We follow every [PSR-2](https://www.php-fig.org/psr/psr-2/) ...
 | 
			
		||||
- ... except camelCase, that's too bad, we use snake_case
 | 
			
		||||
 | 
			
		||||
## Merge Request Process
 | 
			
		||||
 | 
			
		||||
1. Ensure you strip any trailing spaces off
 | 
			
		||||
2. Increase the version numbers in any examples files and the README.md to the new version that this
 | 
			
		||||
   Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
 | 
			
		||||
3. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
 | 
			
		||||
   do not have permission to do that, you may request the second reviewer to merge it for you.
 | 
			
		||||
 | 
			
		||||
## Code of Conduct
 | 
			
		||||
 | 
			
		||||
### Our Pledge
 | 
			
		||||
 | 
			
		||||
In the interest of fostering an open and welcoming environment, we as
 | 
			
		||||
contributors and maintainers pledge to making participation in our project and
 | 
			
		||||
our community a harassment-free experience for everyone, regardless of age, body
 | 
			
		||||
size, disability, ethnicity, gender identity and expression, level of experience,
 | 
			
		||||
nationality, personal appearance, race, religion, or sexual identity and
 | 
			
		||||
orientation.
 | 
			
		||||
 | 
			
		||||
### Our Standards
 | 
			
		||||
 | 
			
		||||
Examples of behavior that contributes to creating a positive environment
 | 
			
		||||
include:
 | 
			
		||||
 | 
			
		||||
* Using welcoming and inclusive language
 | 
			
		||||
* Being respectful of differing viewpoints and experiences
 | 
			
		||||
* Gracefully accepting constructive criticism
 | 
			
		||||
* Focusing on what is best for the community
 | 
			
		||||
* Showing empathy towards other community members
 | 
			
		||||
 | 
			
		||||
Examples of unacceptable behavior by participants include:
 | 
			
		||||
 | 
			
		||||
* The use of sexualized language or imagery and unwelcome sexual attention or
 | 
			
		||||
advances
 | 
			
		||||
* Trolling, insulting/derogatory comments, and personal or political attacks
 | 
			
		||||
* Public or private harassment
 | 
			
		||||
* Publishing others' private information, such as a physical or electronic
 | 
			
		||||
  address, without explicit permission
 | 
			
		||||
* Other conduct which could reasonably be considered inappropriate in a
 | 
			
		||||
  professional setting
 | 
			
		||||
 | 
			
		||||
### Our Responsibilities
 | 
			
		||||
 | 
			
		||||
Project maintainers are responsible for clarifying the standards of acceptable
 | 
			
		||||
behavior and are expected to take appropriate and fair corrective action in
 | 
			
		||||
response to any instances of unacceptable behavior.
 | 
			
		||||
 | 
			
		||||
Project maintainers have the right and responsibility to remove, edit, or
 | 
			
		||||
reject comments, commits, code, wiki edits, issues, and other contributions
 | 
			
		||||
that are not aligned to this Code of Conduct, or to ban temporarily or
 | 
			
		||||
permanently any contributor for other behaviors that they deem inappropriate,
 | 
			
		||||
threatening, offensive, or harmful.
 | 
			
		||||
 | 
			
		||||
### Scope
 | 
			
		||||
 | 
			
		||||
This Code of Conduct applies both within project spaces and in public spaces
 | 
			
		||||
when an individual is representing the project or its community. Examples of
 | 
			
		||||
representing a project or community include using an official project e-mail
 | 
			
		||||
address, posting via an official social media account, or acting as an appointed
 | 
			
		||||
representative at an online or offline event. Representation of a project may be
 | 
			
		||||
further defined and clarified by project maintainers.
 | 
			
		||||
 | 
			
		||||
### Enforcement
 | 
			
		||||
 | 
			
		||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
 | 
			
		||||
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
 | 
			
		||||
complaints will be reviewed and investigated and will result in a response that
 | 
			
		||||
is deemed necessary and appropriate to the circumstances. The project team is
 | 
			
		||||
obligated to maintain confidentiality with regard to the reporter of an incident.
 | 
			
		||||
Further details of specific enforcement policies may be posted separately.
 | 
			
		||||
 | 
			
		||||
Project maintainers who do not follow or enforce the Code of Conduct in good
 | 
			
		||||
faith may face temporary or permanent repercussions as determined by other
 | 
			
		||||
members of the project's leadership.
 | 
			
		||||
 | 
			
		||||
### Attribution
 | 
			
		||||
 | 
			
		||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
 | 
			
		||||
available at [http://contributor-covenant.org/version/1/4][version]
 | 
			
		||||
 | 
			
		||||
[homepage]: http://contributor-covenant.org
 | 
			
		||||
[version]: http://contributor-covenant.org/version/1/4/
 | 
			
		||||
							
								
								
									
										661
									
								
								COPYING
									
									
									
									
									
								
							
							
						
						
									
										661
									
								
								COPYING
									
									
									
									
									
								
							@@ -1,661 +0,0 @@
 | 
			
		||||
                    GNU AFFERO GENERAL PUBLIC LICENSE
 | 
			
		||||
                       Version 3, 19 November 2007
 | 
			
		||||
 | 
			
		||||
 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 | 
			
		||||
 Everyone is permitted to copy and distribute verbatim copies
 | 
			
		||||
 of this license document, but changing it is not allowed.
 | 
			
		||||
 | 
			
		||||
                            Preamble
 | 
			
		||||
 | 
			
		||||
  The GNU Affero General Public License is a free, copyleft license for
 | 
			
		||||
software and other kinds of works, specifically designed to ensure
 | 
			
		||||
cooperation with the community in the case of network server software.
 | 
			
		||||
 | 
			
		||||
  The licenses for most software and other practical works are designed
 | 
			
		||||
to take away your freedom to share and change the works.  By contrast,
 | 
			
		||||
our General Public Licenses are intended to guarantee your freedom to
 | 
			
		||||
share and change all versions of a program--to make sure it remains free
 | 
			
		||||
software for all its users.
 | 
			
		||||
 | 
			
		||||
  When we speak of free software, we are referring to freedom, not
 | 
			
		||||
price.  Our General Public Licenses are designed to make sure that you
 | 
			
		||||
have the freedom to distribute copies of free software (and charge for
 | 
			
		||||
them if you wish), that you receive source code or can get it if you
 | 
			
		||||
want it, that you can change the software or use pieces of it in new
 | 
			
		||||
free programs, and that you know you can do these things.
 | 
			
		||||
 | 
			
		||||
  Developers that use our General Public Licenses protect your rights
 | 
			
		||||
with two steps: (1) assert copyright on the software, and (2) offer
 | 
			
		||||
you this License which gives you legal permission to copy, distribute
 | 
			
		||||
and/or modify the software.
 | 
			
		||||
 | 
			
		||||
  A secondary benefit of defending all users' freedom is that
 | 
			
		||||
improvements made in alternate versions of the program, if they
 | 
			
		||||
receive widespread use, become available for other developers to
 | 
			
		||||
incorporate.  Many developers of free software are heartened and
 | 
			
		||||
encouraged by the resulting cooperation.  However, in the case of
 | 
			
		||||
software used on network servers, this result may fail to come about.
 | 
			
		||||
The GNU General Public License permits making a modified version and
 | 
			
		||||
letting the public access it on a server without ever releasing its
 | 
			
		||||
source code to the public.
 | 
			
		||||
 | 
			
		||||
  The GNU Affero General Public License is designed specifically to
 | 
			
		||||
ensure that, in such cases, the modified source code becomes available
 | 
			
		||||
to the community.  It requires the operator of a network server to
 | 
			
		||||
provide the source code of the modified version running there to the
 | 
			
		||||
users of that server.  Therefore, public use of a modified version, on
 | 
			
		||||
a publicly accessible server, gives the public access to the source
 | 
			
		||||
code of the modified version.
 | 
			
		||||
 | 
			
		||||
  An older license, called the Affero General Public License and
 | 
			
		||||
published by Affero, was designed to accomplish similar goals.  This is
 | 
			
		||||
a different license, not a version of the Affero GPL, but Affero has
 | 
			
		||||
released a new version of the Affero GPL which permits relicensing under
 | 
			
		||||
this license.
 | 
			
		||||
 | 
			
		||||
  The precise terms and conditions for copying, distribution and
 | 
			
		||||
modification follow.
 | 
			
		||||
 | 
			
		||||
                       TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
  0. Definitions.
 | 
			
		||||
 | 
			
		||||
  "This License" refers to version 3 of the GNU Affero General Public License.
 | 
			
		||||
 | 
			
		||||
  "Copyright" also means copyright-like laws that apply to other kinds of
 | 
			
		||||
works, such as semiconductor masks.
 | 
			
		||||
 | 
			
		||||
  "The Program" refers to any copyrightable work licensed under this
 | 
			
		||||
License.  Each licensee is addressed as "you".  "Licensees" and
 | 
			
		||||
"recipients" may be individuals or organizations.
 | 
			
		||||
 | 
			
		||||
  To "modify" a work means to copy from or adapt all or part of the work
 | 
			
		||||
in a fashion requiring copyright permission, other than the making of an
 | 
			
		||||
exact copy.  The resulting work is called a "modified version" of the
 | 
			
		||||
earlier work or a work "based on" the earlier work.
 | 
			
		||||
 | 
			
		||||
  A "covered work" means either the unmodified Program or a work based
 | 
			
		||||
on the Program.
 | 
			
		||||
 | 
			
		||||
  To "propagate" a work means to do anything with it that, without
 | 
			
		||||
permission, would make you directly or secondarily liable for
 | 
			
		||||
infringement under applicable copyright law, except executing it on a
 | 
			
		||||
computer or modifying a private copy.  Propagation includes copying,
 | 
			
		||||
distribution (with or without modification), making available to the
 | 
			
		||||
public, and in some countries other activities as well.
 | 
			
		||||
 | 
			
		||||
  To "convey" a work means any kind of propagation that enables other
 | 
			
		||||
parties to make or receive copies.  Mere interaction with a user through
 | 
			
		||||
a computer network, with no transfer of a copy, is not conveying.
 | 
			
		||||
 | 
			
		||||
  An interactive user interface displays "Appropriate Legal Notices"
 | 
			
		||||
to the extent that it includes a convenient and prominently visible
 | 
			
		||||
feature that (1) displays an appropriate copyright notice, and (2)
 | 
			
		||||
tells the user that there is no warranty for the work (except to the
 | 
			
		||||
extent that warranties are provided), that licensees may convey the
 | 
			
		||||
work under this License, and how to view a copy of this License.  If
 | 
			
		||||
the interface presents a list of user commands or options, such as a
 | 
			
		||||
menu, a prominent item in the list meets this criterion.
 | 
			
		||||
 | 
			
		||||
  1. Source Code.
 | 
			
		||||
 | 
			
		||||
  The "source code" for a work means the preferred form of the work
 | 
			
		||||
for making modifications to it.  "Object code" means any non-source
 | 
			
		||||
form of a work.
 | 
			
		||||
 | 
			
		||||
  A "Standard Interface" means an interface that either is an official
 | 
			
		||||
standard defined by a recognized standards body, or, in the case of
 | 
			
		||||
interfaces specified for a particular programming language, one that
 | 
			
		||||
is widely used among developers working in that language.
 | 
			
		||||
 | 
			
		||||
  The "System Libraries" of an executable work include anything, other
 | 
			
		||||
than the work as a whole, that (a) is included in the normal form of
 | 
			
		||||
packaging a Major Component, but which is not part of that Major
 | 
			
		||||
Component, and (b) serves only to enable use of the work with that
 | 
			
		||||
Major Component, or to implement a Standard Interface for which an
 | 
			
		||||
implementation is available to the public in source code form.  A
 | 
			
		||||
"Major Component", in this context, means a major essential component
 | 
			
		||||
(kernel, window system, and so on) of the specific operating system
 | 
			
		||||
(if any) on which the executable work runs, or a compiler used to
 | 
			
		||||
produce the work, or an object code interpreter used to run it.
 | 
			
		||||
 | 
			
		||||
  The "Corresponding Source" for a work in object code form means all
 | 
			
		||||
the source code needed to generate, install, and (for an executable
 | 
			
		||||
work) run the object code and to modify the work, including scripts to
 | 
			
		||||
control those activities.  However, it does not include the work's
 | 
			
		||||
System Libraries, or general-purpose tools or generally available free
 | 
			
		||||
programs which are used unmodified in performing those activities but
 | 
			
		||||
which are not part of the work.  For example, Corresponding Source
 | 
			
		||||
includes interface definition files associated with source files for
 | 
			
		||||
the work, and the source code for shared libraries and dynamically
 | 
			
		||||
linked subprograms that the work is specifically designed to require,
 | 
			
		||||
such as by intimate data communication or control flow between those
 | 
			
		||||
subprograms and other parts of the work.
 | 
			
		||||
 | 
			
		||||
  The Corresponding Source need not include anything that users
 | 
			
		||||
can regenerate automatically from other parts of the Corresponding
 | 
			
		||||
Source.
 | 
			
		||||
 | 
			
		||||
  The Corresponding Source for a work in source code form is that
 | 
			
		||||
same work.
 | 
			
		||||
 | 
			
		||||
  2. Basic Permissions.
 | 
			
		||||
 | 
			
		||||
  All rights granted under this License are granted for the term of
 | 
			
		||||
copyright on the Program, and are irrevocable provided the stated
 | 
			
		||||
conditions are met.  This License explicitly affirms your unlimited
 | 
			
		||||
permission to run the unmodified Program.  The output from running a
 | 
			
		||||
covered work is covered by this License only if the output, given its
 | 
			
		||||
content, constitutes a covered work.  This License acknowledges your
 | 
			
		||||
rights of fair use or other equivalent, as provided by copyright law.
 | 
			
		||||
 | 
			
		||||
  You may make, run and propagate covered works that you do not
 | 
			
		||||
convey, without conditions so long as your license otherwise remains
 | 
			
		||||
in force.  You may convey covered works to others for the sole purpose
 | 
			
		||||
of having them make modifications exclusively for you, or provide you
 | 
			
		||||
with facilities for running those works, provided that you comply with
 | 
			
		||||
the terms of this License in conveying all material for which you do
 | 
			
		||||
not control copyright.  Those thus making or running the covered works
 | 
			
		||||
for you must do so exclusively on your behalf, under your direction
 | 
			
		||||
and control, on terms that prohibit them from making any copies of
 | 
			
		||||
your copyrighted material outside their relationship with you.
 | 
			
		||||
 | 
			
		||||
  Conveying under any other circumstances is permitted solely under
 | 
			
		||||
the conditions stated below.  Sublicensing is not allowed; section 10
 | 
			
		||||
makes it unnecessary.
 | 
			
		||||
 | 
			
		||||
  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
 | 
			
		||||
 | 
			
		||||
  No covered work shall be deemed part of an effective technological
 | 
			
		||||
measure under any applicable law fulfilling obligations under article
 | 
			
		||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
 | 
			
		||||
similar laws prohibiting or restricting circumvention of such
 | 
			
		||||
measures.
 | 
			
		||||
 | 
			
		||||
  When you convey a covered work, you waive any legal power to forbid
 | 
			
		||||
circumvention of technological measures to the extent such circumvention
 | 
			
		||||
is effected by exercising rights under this License with respect to
 | 
			
		||||
the covered work, and you disclaim any intention to limit operation or
 | 
			
		||||
modification of the work as a means of enforcing, against the work's
 | 
			
		||||
users, your or third parties' legal rights to forbid circumvention of
 | 
			
		||||
technological measures.
 | 
			
		||||
 | 
			
		||||
  4. Conveying Verbatim Copies.
 | 
			
		||||
 | 
			
		||||
  You may convey verbatim copies of the Program's source code as you
 | 
			
		||||
receive it, in any medium, provided that you conspicuously and
 | 
			
		||||
appropriately publish on each copy an appropriate copyright notice;
 | 
			
		||||
keep intact all notices stating that this License and any
 | 
			
		||||
non-permissive terms added in accord with section 7 apply to the code;
 | 
			
		||||
keep intact all notices of the absence of any warranty; and give all
 | 
			
		||||
recipients a copy of this License along with the Program.
 | 
			
		||||
 | 
			
		||||
  You may charge any price or no price for each copy that you convey,
 | 
			
		||||
and you may offer support or warranty protection for a fee.
 | 
			
		||||
 | 
			
		||||
  5. Conveying Modified Source Versions.
 | 
			
		||||
 | 
			
		||||
  You may convey a work based on the Program, or the modifications to
 | 
			
		||||
produce it from the Program, in the form of source code under the
 | 
			
		||||
terms of section 4, provided that you also meet all of these conditions:
 | 
			
		||||
 | 
			
		||||
    a) The work must carry prominent notices stating that you modified
 | 
			
		||||
    it, and giving a relevant date.
 | 
			
		||||
 | 
			
		||||
    b) The work must carry prominent notices stating that it is
 | 
			
		||||
    released under this License and any conditions added under section
 | 
			
		||||
    7.  This requirement modifies the requirement in section 4 to
 | 
			
		||||
    "keep intact all notices".
 | 
			
		||||
 | 
			
		||||
    c) You must license the entire work, as a whole, under this
 | 
			
		||||
    License to anyone who comes into possession of a copy.  This
 | 
			
		||||
    License will therefore apply, along with any applicable section 7
 | 
			
		||||
    additional terms, to the whole of the work, and all its parts,
 | 
			
		||||
    regardless of how they are packaged.  This License gives no
 | 
			
		||||
    permission to license the work in any other way, but it does not
 | 
			
		||||
    invalidate such permission if you have separately received it.
 | 
			
		||||
 | 
			
		||||
    d) If the work has interactive user interfaces, each must display
 | 
			
		||||
    Appropriate Legal Notices; however, if the Program has interactive
 | 
			
		||||
    interfaces that do not display Appropriate Legal Notices, your
 | 
			
		||||
    work need not make them do so.
 | 
			
		||||
 | 
			
		||||
  A compilation of a covered work with other separate and independent
 | 
			
		||||
works, which are not by their nature extensions of the covered work,
 | 
			
		||||
and which are not combined with it such as to form a larger program,
 | 
			
		||||
in or on a volume of a storage or distribution medium, is called an
 | 
			
		||||
"aggregate" if the compilation and its resulting copyright are not
 | 
			
		||||
used to limit the access or legal rights of the compilation's users
 | 
			
		||||
beyond what the individual works permit.  Inclusion of a covered work
 | 
			
		||||
in an aggregate does not cause this License to apply to the other
 | 
			
		||||
parts of the aggregate.
 | 
			
		||||
 | 
			
		||||
  6. Conveying Non-Source Forms.
 | 
			
		||||
 | 
			
		||||
  You may convey a covered work in object code form under the terms
 | 
			
		||||
of sections 4 and 5, provided that you also convey the
 | 
			
		||||
machine-readable Corresponding Source under the terms of this License,
 | 
			
		||||
in one of these ways:
 | 
			
		||||
 | 
			
		||||
    a) Convey the object code in, or embodied in, a physical product
 | 
			
		||||
    (including a physical distribution medium), accompanied by the
 | 
			
		||||
    Corresponding Source fixed on a durable physical medium
 | 
			
		||||
    customarily used for software interchange.
 | 
			
		||||
 | 
			
		||||
    b) Convey the object code in, or embodied in, a physical product
 | 
			
		||||
    (including a physical distribution medium), accompanied by a
 | 
			
		||||
    written offer, valid for at least three years and valid for as
 | 
			
		||||
    long as you offer spare parts or customer support for that product
 | 
			
		||||
    model, to give anyone who possesses the object code either (1) a
 | 
			
		||||
    copy of the Corresponding Source for all the software in the
 | 
			
		||||
    product that is covered by this License, on a durable physical
 | 
			
		||||
    medium customarily used for software interchange, for a price no
 | 
			
		||||
    more than your reasonable cost of physically performing this
 | 
			
		||||
    conveying of source, or (2) access to copy the
 | 
			
		||||
    Corresponding Source from a network server at no charge.
 | 
			
		||||
 | 
			
		||||
    c) Convey individual copies of the object code with a copy of the
 | 
			
		||||
    written offer to provide the Corresponding Source.  This
 | 
			
		||||
    alternative is allowed only occasionally and noncommercially, and
 | 
			
		||||
    only if you received the object code with such an offer, in accord
 | 
			
		||||
    with subsection 6b.
 | 
			
		||||
 | 
			
		||||
    d) Convey the object code by offering access from a designated
 | 
			
		||||
    place (gratis or for a charge), and offer equivalent access to the
 | 
			
		||||
    Corresponding Source in the same way through the same place at no
 | 
			
		||||
    further charge.  You need not require recipients to copy the
 | 
			
		||||
    Corresponding Source along with the object code.  If the place to
 | 
			
		||||
    copy the object code is a network server, the Corresponding Source
 | 
			
		||||
    may be on a different server (operated by you or a third party)
 | 
			
		||||
    that supports equivalent copying facilities, provided you maintain
 | 
			
		||||
    clear directions next to the object code saying where to find the
 | 
			
		||||
    Corresponding Source.  Regardless of what server hosts the
 | 
			
		||||
    Corresponding Source, you remain obligated to ensure that it is
 | 
			
		||||
    available for as long as needed to satisfy these requirements.
 | 
			
		||||
 | 
			
		||||
    e) Convey the object code using peer-to-peer transmission, provided
 | 
			
		||||
    you inform other peers where the object code and Corresponding
 | 
			
		||||
    Source of the work are being offered to the general public at no
 | 
			
		||||
    charge under subsection 6d.
 | 
			
		||||
 | 
			
		||||
  A separable portion of the object code, whose source code is excluded
 | 
			
		||||
from the Corresponding Source as a System Library, need not be
 | 
			
		||||
included in conveying the object code work.
 | 
			
		||||
 | 
			
		||||
  A "User Product" is either (1) a "consumer product", which means any
 | 
			
		||||
tangible personal property which is normally used for personal, family,
 | 
			
		||||
or household purposes, or (2) anything designed or sold for incorporation
 | 
			
		||||
into a dwelling.  In determining whether a product is a consumer product,
 | 
			
		||||
doubtful cases shall be resolved in favor of coverage.  For a particular
 | 
			
		||||
product received by a particular user, "normally used" refers to a
 | 
			
		||||
typical or common use of that class of product, regardless of the status
 | 
			
		||||
of the particular user or of the way in which the particular user
 | 
			
		||||
actually uses, or expects or is expected to use, the product.  A product
 | 
			
		||||
is a consumer product regardless of whether the product has substantial
 | 
			
		||||
commercial, industrial or non-consumer uses, unless such uses represent
 | 
			
		||||
the only significant mode of use of the product.
 | 
			
		||||
 | 
			
		||||
  "Installation Information" for a User Product means any methods,
 | 
			
		||||
procedures, authorization keys, or other information required to install
 | 
			
		||||
and execute modified versions of a covered work in that User Product from
 | 
			
		||||
a modified version of its Corresponding Source.  The information must
 | 
			
		||||
suffice to ensure that the continued functioning of the modified object
 | 
			
		||||
code is in no case prevented or interfered with solely because
 | 
			
		||||
modification has been made.
 | 
			
		||||
 | 
			
		||||
  If you convey an object code work under this section in, or with, or
 | 
			
		||||
specifically for use in, a User Product, and the conveying occurs as
 | 
			
		||||
part of a transaction in which the right of possession and use of the
 | 
			
		||||
User Product is transferred to the recipient in perpetuity or for a
 | 
			
		||||
fixed term (regardless of how the transaction is characterized), the
 | 
			
		||||
Corresponding Source conveyed under this section must be accompanied
 | 
			
		||||
by the Installation Information.  But this requirement does not apply
 | 
			
		||||
if neither you nor any third party retains the ability to install
 | 
			
		||||
modified object code on the User Product (for example, the work has
 | 
			
		||||
been installed in ROM).
 | 
			
		||||
 | 
			
		||||
  The requirement to provide Installation Information does not include a
 | 
			
		||||
requirement to continue to provide support service, warranty, or updates
 | 
			
		||||
for a work that has been modified or installed by the recipient, or for
 | 
			
		||||
the User Product in which it has been modified or installed.  Access to a
 | 
			
		||||
network may be denied when the modification itself materially and
 | 
			
		||||
adversely affects the operation of the network or violates the rules and
 | 
			
		||||
protocols for communication across the network.
 | 
			
		||||
 | 
			
		||||
  Corresponding Source conveyed, and Installation Information provided,
 | 
			
		||||
in accord with this section must be in a format that is publicly
 | 
			
		||||
documented (and with an implementation available to the public in
 | 
			
		||||
source code form), and must require no special password or key for
 | 
			
		||||
unpacking, reading or copying.
 | 
			
		||||
 | 
			
		||||
  7. Additional Terms.
 | 
			
		||||
 | 
			
		||||
  "Additional permissions" are terms that supplement the terms of this
 | 
			
		||||
License by making exceptions from one or more of its conditions.
 | 
			
		||||
Additional permissions that are applicable to the entire Program shall
 | 
			
		||||
be treated as though they were included in this License, to the extent
 | 
			
		||||
that they are valid under applicable law.  If additional permissions
 | 
			
		||||
apply only to part of the Program, that part may be used separately
 | 
			
		||||
under those permissions, but the entire Program remains governed by
 | 
			
		||||
this License without regard to the additional permissions.
 | 
			
		||||
 | 
			
		||||
  When you convey a copy of a covered work, you may at your option
 | 
			
		||||
remove any additional permissions from that copy, or from any part of
 | 
			
		||||
it.  (Additional permissions may be written to require their own
 | 
			
		||||
removal in certain cases when you modify the work.)  You may place
 | 
			
		||||
additional permissions on material, added by you to a covered work,
 | 
			
		||||
for which you have or can give appropriate copyright permission.
 | 
			
		||||
 | 
			
		||||
  Notwithstanding any other provision of this License, for material you
 | 
			
		||||
add to a covered work, you may (if authorized by the copyright holders of
 | 
			
		||||
that material) supplement the terms of this License with terms:
 | 
			
		||||
 | 
			
		||||
    a) Disclaiming warranty or limiting liability differently from the
 | 
			
		||||
    terms of sections 15 and 16 of this License; or
 | 
			
		||||
 | 
			
		||||
    b) Requiring preservation of specified reasonable legal notices or
 | 
			
		||||
    author attributions in that material or in the Appropriate Legal
 | 
			
		||||
    Notices displayed by works containing it; or
 | 
			
		||||
 | 
			
		||||
    c) Prohibiting misrepresentation of the origin of that material, or
 | 
			
		||||
    requiring that modified versions of such material be marked in
 | 
			
		||||
    reasonable ways as different from the original version; or
 | 
			
		||||
 | 
			
		||||
    d) Limiting the use for publicity purposes of names of licensors or
 | 
			
		||||
    authors of the material; or
 | 
			
		||||
 | 
			
		||||
    e) Declining to grant rights under trademark law for use of some
 | 
			
		||||
    trade names, trademarks, or service marks; or
 | 
			
		||||
 | 
			
		||||
    f) Requiring indemnification of licensors and authors of that
 | 
			
		||||
    material by anyone who conveys the material (or modified versions of
 | 
			
		||||
    it) with contractual assumptions of liability to the recipient, for
 | 
			
		||||
    any liability that these contractual assumptions directly impose on
 | 
			
		||||
    those licensors and authors.
 | 
			
		||||
 | 
			
		||||
  All other non-permissive additional terms are considered "further
 | 
			
		||||
restrictions" within the meaning of section 10.  If the Program as you
 | 
			
		||||
received it, or any part of it, contains a notice stating that it is
 | 
			
		||||
governed by this License along with a term that is a further
 | 
			
		||||
restriction, you may remove that term.  If a license document contains
 | 
			
		||||
a further restriction but permits relicensing or conveying under this
 | 
			
		||||
License, you may add to a covered work material governed by the terms
 | 
			
		||||
of that license document, provided that the further restriction does
 | 
			
		||||
not survive such relicensing or conveying.
 | 
			
		||||
 | 
			
		||||
  If you add terms to a covered work in accord with this section, you
 | 
			
		||||
must place, in the relevant source files, a statement of the
 | 
			
		||||
additional terms that apply to those files, or a notice indicating
 | 
			
		||||
where to find the applicable terms.
 | 
			
		||||
 | 
			
		||||
  Additional terms, permissive or non-permissive, may be stated in the
 | 
			
		||||
form of a separately written license, or stated as exceptions;
 | 
			
		||||
the above requirements apply either way.
 | 
			
		||||
 | 
			
		||||
  8. Termination.
 | 
			
		||||
 | 
			
		||||
  You may not propagate or modify a covered work except as expressly
 | 
			
		||||
provided under this License.  Any attempt otherwise to propagate or
 | 
			
		||||
modify it is void, and will automatically terminate your rights under
 | 
			
		||||
this License (including any patent licenses granted under the third
 | 
			
		||||
paragraph of section 11).
 | 
			
		||||
 | 
			
		||||
  However, if you cease all violation of this License, then your
 | 
			
		||||
license from a particular copyright holder is reinstated (a)
 | 
			
		||||
provisionally, unless and until the copyright holder explicitly and
 | 
			
		||||
finally terminates your license, and (b) permanently, if the copyright
 | 
			
		||||
holder fails to notify you of the violation by some reasonable means
 | 
			
		||||
prior to 60 days after the cessation.
 | 
			
		||||
 | 
			
		||||
  Moreover, your license from a particular copyright holder is
 | 
			
		||||
reinstated permanently if the copyright holder notifies you of the
 | 
			
		||||
violation by some reasonable means, this is the first time you have
 | 
			
		||||
received notice of violation of this License (for any work) from that
 | 
			
		||||
copyright holder, and you cure the violation prior to 30 days after
 | 
			
		||||
your receipt of the notice.
 | 
			
		||||
 | 
			
		||||
  Termination of your rights under this section does not terminate the
 | 
			
		||||
licenses of parties who have received copies or rights from you under
 | 
			
		||||
this License.  If your rights have been terminated and not permanently
 | 
			
		||||
reinstated, you do not qualify to receive new licenses for the same
 | 
			
		||||
material under section 10.
 | 
			
		||||
 | 
			
		||||
  9. Acceptance Not Required for Having Copies.
 | 
			
		||||
 | 
			
		||||
  You are not required to accept this License in order to receive or
 | 
			
		||||
run a copy of the Program.  Ancillary propagation of a covered work
 | 
			
		||||
occurring solely as a consequence of using peer-to-peer transmission
 | 
			
		||||
to receive a copy likewise does not require acceptance.  However,
 | 
			
		||||
nothing other than this License grants you permission to propagate or
 | 
			
		||||
modify any covered work.  These actions infringe copyright if you do
 | 
			
		||||
not accept this License.  Therefore, by modifying or propagating a
 | 
			
		||||
covered work, you indicate your acceptance of this License to do so.
 | 
			
		||||
 | 
			
		||||
  10. Automatic Licensing of Downstream Recipients.
 | 
			
		||||
 | 
			
		||||
  Each time you convey a covered work, the recipient automatically
 | 
			
		||||
receives a license from the original licensors, to run, modify and
 | 
			
		||||
propagate that work, subject to this License.  You are not responsible
 | 
			
		||||
for enforcing compliance by third parties with this License.
 | 
			
		||||
 | 
			
		||||
  An "entity transaction" is a transaction transferring control of an
 | 
			
		||||
organization, or substantially all assets of one, or subdividing an
 | 
			
		||||
organization, or merging organizations.  If propagation of a covered
 | 
			
		||||
work results from an entity transaction, each party to that
 | 
			
		||||
transaction who receives a copy of the work also receives whatever
 | 
			
		||||
licenses to the work the party's predecessor in interest had or could
 | 
			
		||||
give under the previous paragraph, plus a right to possession of the
 | 
			
		||||
Corresponding Source of the work from the predecessor in interest, if
 | 
			
		||||
the predecessor has it or can get it with reasonable efforts.
 | 
			
		||||
 | 
			
		||||
  You may not impose any further restrictions on the exercise of the
 | 
			
		||||
rights granted or affirmed under this License.  For example, you may
 | 
			
		||||
not impose a license fee, royalty, or other charge for exercise of
 | 
			
		||||
rights granted under this License, and you may not initiate litigation
 | 
			
		||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
 | 
			
		||||
any patent claim is infringed by making, using, selling, offering for
 | 
			
		||||
sale, or importing the Program or any portion of it.
 | 
			
		||||
 | 
			
		||||
  11. Patents.
 | 
			
		||||
 | 
			
		||||
  A "contributor" is a copyright holder who authorizes use under this
 | 
			
		||||
License of the Program or a work on which the Program is based.  The
 | 
			
		||||
work thus licensed is called the contributor's "contributor version".
 | 
			
		||||
 | 
			
		||||
  A contributor's "essential patent claims" are all patent claims
 | 
			
		||||
owned or controlled by the contributor, whether already acquired or
 | 
			
		||||
hereafter acquired, that would be infringed by some manner, permitted
 | 
			
		||||
by this License, of making, using, or selling its contributor version,
 | 
			
		||||
but do not include claims that would be infringed only as a
 | 
			
		||||
consequence of further modification of the contributor version.  For
 | 
			
		||||
purposes of this definition, "control" includes the right to grant
 | 
			
		||||
patent sublicenses in a manner consistent with the requirements of
 | 
			
		||||
this License.
 | 
			
		||||
 | 
			
		||||
  Each contributor grants you a non-exclusive, worldwide, royalty-free
 | 
			
		||||
patent license under the contributor's essential patent claims, to
 | 
			
		||||
make, use, sell, offer for sale, import and otherwise run, modify and
 | 
			
		||||
propagate the contents of its contributor version.
 | 
			
		||||
 | 
			
		||||
  In the following three paragraphs, a "patent license" is any express
 | 
			
		||||
agreement or commitment, however denominated, not to enforce a patent
 | 
			
		||||
(such as an express permission to practice a patent or covenant not to
 | 
			
		||||
sue for patent infringement).  To "grant" such a patent license to a
 | 
			
		||||
party means to make such an agreement or commitment not to enforce a
 | 
			
		||||
patent against the party.
 | 
			
		||||
 | 
			
		||||
  If you convey a covered work, knowingly relying on a patent license,
 | 
			
		||||
and the Corresponding Source of the work is not available for anyone
 | 
			
		||||
to copy, free of charge and under the terms of this License, through a
 | 
			
		||||
publicly available network server or other readily accessible means,
 | 
			
		||||
then you must either (1) cause the Corresponding Source to be so
 | 
			
		||||
available, or (2) arrange to deprive yourself of the benefit of the
 | 
			
		||||
patent license for this particular work, or (3) arrange, in a manner
 | 
			
		||||
consistent with the requirements of this License, to extend the patent
 | 
			
		||||
license to downstream recipients.  "Knowingly relying" means you have
 | 
			
		||||
actual knowledge that, but for the patent license, your conveying the
 | 
			
		||||
covered work in a country, or your recipient's use of the covered work
 | 
			
		||||
in a country, would infringe one or more identifiable patents in that
 | 
			
		||||
country that you have reason to believe are valid.
 | 
			
		||||
 | 
			
		||||
  If, pursuant to or in connection with a single transaction or
 | 
			
		||||
arrangement, you convey, or propagate by procuring conveyance of, a
 | 
			
		||||
covered work, and grant a patent license to some of the parties
 | 
			
		||||
receiving the covered work authorizing them to use, propagate, modify
 | 
			
		||||
or convey a specific copy of the covered work, then the patent license
 | 
			
		||||
you grant is automatically extended to all recipients of the covered
 | 
			
		||||
work and works based on it.
 | 
			
		||||
 | 
			
		||||
  A patent license is "discriminatory" if it does not include within
 | 
			
		||||
the scope of its coverage, prohibits the exercise of, or is
 | 
			
		||||
conditioned on the non-exercise of one or more of the rights that are
 | 
			
		||||
specifically granted under this License.  You may not convey a covered
 | 
			
		||||
work if you are a party to an arrangement with a third party that is
 | 
			
		||||
in the business of distributing software, under which you make payment
 | 
			
		||||
to the third party based on the extent of your activity of conveying
 | 
			
		||||
the work, and under which the third party grants, to any of the
 | 
			
		||||
parties who would receive the covered work from you, a discriminatory
 | 
			
		||||
patent license (a) in connection with copies of the covered work
 | 
			
		||||
conveyed by you (or copies made from those copies), or (b) primarily
 | 
			
		||||
for and in connection with specific products or compilations that
 | 
			
		||||
contain the covered work, unless you entered into that arrangement,
 | 
			
		||||
or that patent license was granted, prior to 28 March 2007.
 | 
			
		||||
 | 
			
		||||
  Nothing in this License shall be construed as excluding or limiting
 | 
			
		||||
any implied license or other defenses to infringement that may
 | 
			
		||||
otherwise be available to you under applicable patent law.
 | 
			
		||||
 | 
			
		||||
  12. No Surrender of Others' Freedom.
 | 
			
		||||
 | 
			
		||||
  If conditions are imposed on you (whether by court order, agreement or
 | 
			
		||||
otherwise) that contradict the conditions of this License, they do not
 | 
			
		||||
excuse you from the conditions of this License.  If you cannot convey a
 | 
			
		||||
covered work so as to satisfy simultaneously your obligations under this
 | 
			
		||||
License and any other pertinent obligations, then as a consequence you may
 | 
			
		||||
not convey it at all.  For example, if you agree to terms that obligate you
 | 
			
		||||
to collect a royalty for further conveying from those to whom you convey
 | 
			
		||||
the Program, the only way you could satisfy both those terms and this
 | 
			
		||||
License would be to refrain entirely from conveying the Program.
 | 
			
		||||
 | 
			
		||||
  13. Remote Network Interaction; Use with the GNU General Public License.
 | 
			
		||||
 | 
			
		||||
  Notwithstanding any other provision of this License, if you modify the
 | 
			
		||||
Program, your modified version must prominently offer all users
 | 
			
		||||
interacting with it remotely through a computer network (if your version
 | 
			
		||||
supports such interaction) an opportunity to receive the Corresponding
 | 
			
		||||
Source of your version by providing access to the Corresponding Source
 | 
			
		||||
from a network server at no charge, through some standard or customary
 | 
			
		||||
means of facilitating copying of software.  This Corresponding Source
 | 
			
		||||
shall include the Corresponding Source for any work covered by version 3
 | 
			
		||||
of the GNU General Public License that is incorporated pursuant to the
 | 
			
		||||
following paragraph.
 | 
			
		||||
 | 
			
		||||
  Notwithstanding any other provision of this License, you have
 | 
			
		||||
permission to link or combine any covered work with a work licensed
 | 
			
		||||
under version 3 of the GNU General Public License into a single
 | 
			
		||||
combined work, and to convey the resulting work.  The terms of this
 | 
			
		||||
License will continue to apply to the part which is the covered work,
 | 
			
		||||
but the work with which it is combined will remain governed by version
 | 
			
		||||
3 of the GNU General Public License.
 | 
			
		||||
 | 
			
		||||
  14. Revised Versions of this License.
 | 
			
		||||
 | 
			
		||||
  The Free Software Foundation may publish revised and/or new versions of
 | 
			
		||||
the GNU Affero General Public License from time to time.  Such new versions
 | 
			
		||||
will be similar in spirit to the present version, but may differ in detail to
 | 
			
		||||
address new problems or concerns.
 | 
			
		||||
 | 
			
		||||
  Each version is given a distinguishing version number.  If the
 | 
			
		||||
Program specifies that a certain numbered version of the GNU Affero General
 | 
			
		||||
Public License "or any later version" applies to it, you have the
 | 
			
		||||
option of following the terms and conditions either of that numbered
 | 
			
		||||
version or of any later version published by the Free Software
 | 
			
		||||
Foundation.  If the Program does not specify a version number of the
 | 
			
		||||
GNU Affero General Public License, you may choose any version ever published
 | 
			
		||||
by the Free Software Foundation.
 | 
			
		||||
 | 
			
		||||
  If the Program specifies that a proxy can decide which future
 | 
			
		||||
versions of the GNU Affero General Public License can be used, that proxy's
 | 
			
		||||
public statement of acceptance of a version permanently authorizes you
 | 
			
		||||
to choose that version for the Program.
 | 
			
		||||
 | 
			
		||||
  Later license versions may give you additional or different
 | 
			
		||||
permissions.  However, no additional obligations are imposed on any
 | 
			
		||||
author or copyright holder as a result of your choosing to follow a
 | 
			
		||||
later version.
 | 
			
		||||
 | 
			
		||||
  15. Disclaimer of Warranty.
 | 
			
		||||
 | 
			
		||||
  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
 | 
			
		||||
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
 | 
			
		||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
 | 
			
		||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
 | 
			
		||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 | 
			
		||||
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
 | 
			
		||||
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
 | 
			
		||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
 | 
			
		||||
 | 
			
		||||
  16. Limitation of Liability.
 | 
			
		||||
 | 
			
		||||
  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 | 
			
		||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
 | 
			
		||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
 | 
			
		||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
 | 
			
		||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
 | 
			
		||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
 | 
			
		||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
 | 
			
		||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
 | 
			
		||||
SUCH DAMAGES.
 | 
			
		||||
 | 
			
		||||
  17. Interpretation of Sections 15 and 16.
 | 
			
		||||
 | 
			
		||||
  If the disclaimer of warranty and limitation of liability provided
 | 
			
		||||
above cannot be given local legal effect according to their terms,
 | 
			
		||||
reviewing courts shall apply local law that most closely approximates
 | 
			
		||||
an absolute waiver of all civil liability in connection with the
 | 
			
		||||
Program, unless a warranty or assumption of liability accompanies a
 | 
			
		||||
copy of the Program in return for a fee.
 | 
			
		||||
 | 
			
		||||
                     END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
            How to Apply These Terms to Your New Programs
 | 
			
		||||
 | 
			
		||||
  If you develop a new program, and you want it to be of the greatest
 | 
			
		||||
possible use to the public, the best way to achieve this is to make it
 | 
			
		||||
free software which everyone can redistribute and change under these terms.
 | 
			
		||||
 | 
			
		||||
  To do so, attach the following notices to the program.  It is safest
 | 
			
		||||
to attach them to the start of each source file to most effectively
 | 
			
		||||
state the exclusion of warranty; and each file should have at least
 | 
			
		||||
the "copyright" line and a pointer to where the full notice is found.
 | 
			
		||||
 | 
			
		||||
    <one line to give the program's name and a brief idea of what it does.>
 | 
			
		||||
    Copyright (C) <year>  <name of author>
 | 
			
		||||
 | 
			
		||||
    This program is free software: you can redistribute it and/or modify
 | 
			
		||||
    it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
    the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
    (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
    This program is distributed in the hope that it will be useful,
 | 
			
		||||
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
    GNU Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
    You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
Also add information on how to contact you by electronic and paper mail.
 | 
			
		||||
 | 
			
		||||
  If your software can interact with users remotely through a computer
 | 
			
		||||
network, you should also make sure that it provides a way for users to
 | 
			
		||||
get its source.  For example, if your program is a web application, its
 | 
			
		||||
interface could display a "Source" link that leads users to an archive
 | 
			
		||||
of the code.  There are many ways you could offer source, and different
 | 
			
		||||
solutions will be better for different programs; see section 13 for the
 | 
			
		||||
specific requirements.
 | 
			
		||||
 | 
			
		||||
  You should also get your employer (if you work as a programmer) or school,
 | 
			
		||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
 | 
			
		||||
For more information on this, and how to apply and follow the GNU AGPL, see
 | 
			
		||||
<http://www.gnu.org/licenses/>.
 | 
			
		||||
							
								
								
									
										75
									
								
								README.md
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										75
									
								
								README.md
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -1,73 +1,2 @@
 | 
			
		||||
# ActivityPub plugin for GNU Social 1.0 Alpha
 | 
			
		||||
2018
 | 
			
		||||
 | 
			
		||||
(c) Free Software Foundation, Inc
 | 
			
		||||
 | 
			
		||||
This is the README file for GNU Social's ActivityPub plugin.
 | 
			
		||||
It includes general information about the plugin.
 | 
			
		||||
 | 
			
		||||
## About
 | 
			
		||||
 | 
			
		||||
This plugin adds [ActivityPub](https://www.w3.org/TR/activitypub/) support to
 | 
			
		||||
GNU Social.
 | 
			
		||||
 | 
			
		||||
## Setup
 | 
			
		||||
 | 
			
		||||
1. Put all files in /plugins/ActivityPub
 | 
			
		||||
 | 
			
		||||
2. Add `addPlugin('ActivityPub');` to your /config.php file.
 | 
			
		||||
 | 
			
		||||
3. For better performance consider both:
 | 
			
		||||
   - disabling checkschema (instructions in GNU social's config.php), but don't forget to run it when updating plugins (including ActivityPub plugin)
 | 
			
		||||
   - installing Chimo's Redis plugin
 | 
			
		||||
 | 
			
		||||
##  For testing, (shouldn't be used in production)
 | 
			
		||||
 | 
			
		||||
        composer install && vendor/bin/phpunit
 | 
			
		||||
 | 
			
		||||
## Built With
 | 
			
		||||
 | 
			
		||||
* [PHPUnit](https://phpunit.de/) - Automated tests
 | 
			
		||||
 | 
			
		||||
## Contributing
 | 
			
		||||
 | 
			
		||||
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting merge requests to us.
 | 
			
		||||
 | 
			
		||||
## Versioning
 | 
			
		||||
 | 
			
		||||
We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://git.gnu.io/gnu/GS-ActivityPub-Plugin/tags).
 | 
			
		||||
 | 
			
		||||
## Credits
 | 
			
		||||
 | 
			
		||||
* **[Diogo Cordeiro](https://www.diogo.site/)**
 | 
			
		||||
 | 
			
		||||
See also the list of [contributors](https://git.gnu.io/gnu/GS-ActivityPub-Plugin/contributors) who participated in this project.
 | 
			
		||||
 | 
			
		||||
## Extra special thanks
 | 
			
		||||
 | 
			
		||||
* **[Daniel Supernault](https://github.com/dansup)**
 | 
			
		||||
* **[Mikael Nordfeldth](https://mmn-o.se/)**
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU Affero General Public License as
 | 
			
		||||
published by the Free Software Foundation, either version 3 of the
 | 
			
		||||
License, or (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful, but
 | 
			
		||||
WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
			
		||||
Affero General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU Affero General Public
 | 
			
		||||
License along with this program, in the file "COPYING".  If not, see
 | 
			
		||||
<http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
    IMPORTANT NOTE: The GNU Affero General Public License (AGPL) has
 | 
			
		||||
    *different requirements* from the "regular" GPL. In particular, if
 | 
			
		||||
    you make modifications to the plugin source code on your server,
 | 
			
		||||
    you *MUST MAKE AVAILABLE* the modified version of the source code
 | 
			
		||||
    to your users under the same license. This is a legal requirement
 | 
			
		||||
    of using the software, and if you do not wish to share your
 | 
			
		||||
    modifications, *YOU MAY NOT USE THIS PLUGIN*.
 | 
			
		||||
# ActivityPub GNU/Social Plugin
 | 
			
		||||
Warning: This is still a work in progress, not every ActivityPub property is fully implemented yet.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										105
									
								
								actions/apactorfollowers.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										105
									
								
								actions/apactorfollowers.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -20,12 +20,13 @@
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -45,91 +46,69 @@ class apActorFollowersAction extends ManagedAction
 | 
			
		||||
        /**
 | 
			
		||||
         * Handle the Followers Collection request
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @return void
 | 
			
		||||
         */
 | 
			
		||||
    protected function handle()
 | 
			
		||||
        protected function handle ()
 | 
			
		||||
        {
 | 
			
		||||
                $nickname = $this->trimmed ('nickname');
 | 
			
		||||
                try {
 | 
			
		||||
            $profile = Profile::getByID($this->trimmed('id'));
 | 
			
		||||
            $profile_id = $profile->getID();
 | 
			
		||||
                        $user    = User::getByNickname ($nickname);
 | 
			
		||||
                        $profile = $user->getProfile ();
 | 
			
		||||
                        $url     = $profile->profileurl;
 | 
			
		||||
                } catch (Exception $e) {
 | 
			
		||||
            ActivityPubReturn::error('Invalid Actor URI.', 404);
 | 
			
		||||
                        ActivityPubReturn::error ('Invalid username.');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        if (!$profile->isLocal()) {
 | 
			
		||||
            ActivityPubReturn::error("This is not a local user.", 403);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!isset($_GET["page"])) {
 | 
			
		||||
            $page = 0;
 | 
			
		||||
                if (!isset ($_GET["page"])) {
 | 
			
		||||
                        $page = 1;
 | 
			
		||||
                } else {
 | 
			
		||||
            $page = intval($this->trimmed('page'));
 | 
			
		||||
                        $page = intval ($this->trimmed ('page'));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        if ($page < 0) {
 | 
			
		||||
            ActivityPubReturn::error('Invalid page number.');
 | 
			
		||||
                if ($page <= 0) {
 | 
			
		||||
                        ActivityPubReturn::error ('Invalid page number.');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /* Fetch Followers */
 | 
			
		||||
                try {
 | 
			
		||||
                        $since = ($page - 1) * PROFILES_PER_MINILIST;
 | 
			
		||||
                        $limit = (($page - 1) == 0 ? 1 : $page) * PROFILES_PER_MINILIST;
 | 
			
		||||
                        $sub   = $profile->getSubscribers ($since, $limit);
 | 
			
		||||
                } catch (NoResultException $e) {
 | 
			
		||||
                        ActivityPubReturn::error ('This user has no followers.');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /* Calculate total items */
 | 
			
		||||
        $total_subs  = $profile->subscriberCount();
 | 
			
		||||
        $total_pages = ceil($total_subs / PROFILES_PER_MINILIST);
 | 
			
		||||
                $total_subs  = $profile->subscriberCount ();
 | 
			
		||||
                $total_pages = ceil ($total_subs / PROFILES_PER_MINILIST);
 | 
			
		||||
 | 
			
		||||
                if ($total_pages == 0) {
 | 
			
		||||
                        ActivityPubReturn::error ('This user has no followers.');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($page > $total_pages) {
 | 
			
		||||
                        ActivityPubReturn::error ("There are only {$total_pages} pages.");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /* Get followers' URLs */
 | 
			
		||||
                $subs = array ();
 | 
			
		||||
                while ($sub->fetch ()) {
 | 
			
		||||
                        $subs[] = $sub->profileurl;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $res = [
 | 
			
		||||
                  '@context'     => [
 | 
			
		||||
                    "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                    "https://w3id.org/security/v1",
 | 
			
		||||
                  ],
 | 
			
		||||
            'id'           => common_local_url('apActorFollowers', ['id' => $profile_id]).(($page != 0) ? '?page='.$page : ''),
 | 
			
		||||
                  'id'           => "{$url}/followers.json",
 | 
			
		||||
                  'type'         => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'),
 | 
			
		||||
            'totalItems'   => $total_subs
 | 
			
		||||
                  'totalItems'   => $total_subs,
 | 
			
		||||
                  'next'         => $page+1 > $total_pages ? null : "{$url}/followers.json?page=".($page+1 == 1 ? 2 : $page+1),
 | 
			
		||||
                  'prev'         => $page == 1 ? null : "{$url}/followers.json?page=".($page-1 <= 0 ? 1 : $page-1),
 | 
			
		||||
                  'orderedItems' => $subs
 | 
			
		||||
                ];
 | 
			
		||||
 | 
			
		||||
        if ($page == 0) {
 | 
			
		||||
            $res['first'] = common_local_url('apActorFollowers', ['id' => $profile_id]).'?page=1';
 | 
			
		||||
        } else {
 | 
			
		||||
            $res['orderedItems'] = $this->generate_followers($profile, $since, $limit);
 | 
			
		||||
            $res['partOf'] = common_local_url('apActorFollowers', ['id' => $profile_id]);
 | 
			
		||||
 | 
			
		||||
            if ($page+1 < $total_pages) {
 | 
			
		||||
                $res['next'] = common_local_url('apActorFollowers', ['id' => $profile_id]).'page='.($page+1 == 1 ? 2 : $page+1);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ($page > 1) {
 | 
			
		||||
                $res['prev'] = common_local_url('apActorFollowers', ['id' => $profile_id]).'?page='.($page-1 <= 0 ? 1 : $page-1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ActivityPubReturn::answer($res);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates a list of stalkers for a given profile.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Profile $profile
 | 
			
		||||
     * @param int32 $since
 | 
			
		||||
     * @param int32 $limit
 | 
			
		||||
     * @return Array of URIs
 | 
			
		||||
     */
 | 
			
		||||
    public static function generate_followers($profile, $since, $limit)
 | 
			
		||||
    {
 | 
			
		||||
        /* Fetch Followers */
 | 
			
		||||
        try {
 | 
			
		||||
            $sub = $profile->getSubscribers($since, $limit);
 | 
			
		||||
        } catch (NoResultException $e) {
 | 
			
		||||
            // Just let the exception go on its merry way
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* Get followers' URLs */
 | 
			
		||||
        $subs = [];
 | 
			
		||||
        while ($sub->fetch()) {
 | 
			
		||||
            $subs[] = ActivityPubPlugin::actor_uri($sub);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $subs;
 | 
			
		||||
                ActivityPubReturn::answer ($res);
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										102
									
								
								actions/apactorfollowing.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										102
									
								
								actions/apactorfollowing.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -20,12 +20,13 @@
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -45,90 +46,69 @@ class apActorFollowingAction extends ManagedAction
 | 
			
		||||
        /**
 | 
			
		||||
         * Handle the Following Collection request
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @return void
 | 
			
		||||
         */
 | 
			
		||||
    protected function handle()
 | 
			
		||||
        protected function handle ()
 | 
			
		||||
        {
 | 
			
		||||
                $nickname = $this->trimmed ('nickname');
 | 
			
		||||
                try {
 | 
			
		||||
            $profile = Profile::getByID($this->trimmed('id'));
 | 
			
		||||
            $profile_id = $profile->getID();
 | 
			
		||||
                        $user    = User::getByNickname ($nickname);
 | 
			
		||||
                        $profile = $user->getProfile ();
 | 
			
		||||
                        $url     = $profile->profileurl;
 | 
			
		||||
                } catch (Exception $e) {
 | 
			
		||||
            ActivityPubReturn::error('Invalid Actor URI.', 404);
 | 
			
		||||
                        ActivityPubReturn::error ('Invalid username.');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        if (!$profile->isLocal()) {
 | 
			
		||||
            ActivityPubReturn::error("This is not a local user.", 403);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!isset($_GET["page"])) {
 | 
			
		||||
            $page = 0;
 | 
			
		||||
                if (!isset ($_GET["page"])) {
 | 
			
		||||
                        $page = 1;
 | 
			
		||||
                } else {
 | 
			
		||||
            $page = intval($this->trimmed('page'));
 | 
			
		||||
                        $page = intval ($this->trimmed ('page'));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        if ($page < 0) {
 | 
			
		||||
            ActivityPubReturn::error('Invalid page number.');
 | 
			
		||||
                if ($page <= 0) {
 | 
			
		||||
                        ActivityPubReturn::error ('Invalid page number.');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /* Fetch Following */
 | 
			
		||||
                try {
 | 
			
		||||
                        $since = ($page - 1) * PROFILES_PER_MINILIST;
 | 
			
		||||
                        $limit = (($page - 1) == 0 ? 1 : $page) * PROFILES_PER_MINILIST;
 | 
			
		||||
                        $sub   = $profile->getSubscribed ($since, $limit);
 | 
			
		||||
                } catch (NoResultException $e) {
 | 
			
		||||
                        ActivityPubReturn::error ('This user is not following anyone.');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /* Calculate total items */
 | 
			
		||||
                $total_subs  = $profile->subscriptionCount();
 | 
			
		||||
        $total_pages = ceil($total_subs / PROFILES_PER_MINILIST);
 | 
			
		||||
                $total_pages = ceil ($total_subs / PROFILES_PER_MINILIST);
 | 
			
		||||
 | 
			
		||||
                if ($total_pages == 0) {
 | 
			
		||||
                        ActivityPubReturn::error ('This user is not following anyone.');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($page > $total_pages) {
 | 
			
		||||
                        ActivityPubReturn::error ("There are only {$total_pages} pages.");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /* Get followed' URLs */
 | 
			
		||||
                $subs = array ();
 | 
			
		||||
                while ($sub->fetch ()) {
 | 
			
		||||
                        $subs[] = $sub->profileurl;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $res = [
 | 
			
		||||
                  '@context'     => [
 | 
			
		||||
                    "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                    "https://w3id.org/security/v1",
 | 
			
		||||
                  ],
 | 
			
		||||
            'id'           => common_local_url('apActorFollowing', ['id' => $profile_id]).(($page != 0) ? '?page='.$page : ''),
 | 
			
		||||
                  'id'           => "{$url}/following.json",
 | 
			
		||||
                  'type'         => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'),
 | 
			
		||||
            'totalItems'   => $total_subs
 | 
			
		||||
                  'totalItems'   => $total_subs,
 | 
			
		||||
                  'next'         => $page+1 > $total_pages ? null : "{$url}/followers.json?page=".($page+1 == 1 ? 2 : $page+1),
 | 
			
		||||
                  'prev'         => $page == 1 ? null : "{$url}/followers.json?page=".($page-1 <= 0 ? 1 : $page-1),
 | 
			
		||||
                  'orderedItems' => $subs
 | 
			
		||||
                ];
 | 
			
		||||
 | 
			
		||||
        if ($page == 0) {
 | 
			
		||||
            $res['first'] = common_local_url('apActorFollowing', ['id' => $profile_id]).'?page=1';
 | 
			
		||||
        } else {
 | 
			
		||||
            $res['orderedItems'] = $this->generate_following($profile, $since, $limit);
 | 
			
		||||
            $res['partOf'] = common_local_url('apActorFollowing', ['id' => $profile_id]);
 | 
			
		||||
 | 
			
		||||
            if ($page+1 < $total_pages) {
 | 
			
		||||
                $res['next'] = common_local_url('apActorFollowing', ['id' => $profile_id]).'page='.($page+1 == 1 ? 2 : $page+1);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ($page > 1) {
 | 
			
		||||
                $res['prev'] = common_local_url('apActorFollowing', ['id' => $profile_id]).'?page='.($page-1 <= 0 ? 1 : $page-1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ActivityPubReturn::answer($res);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates the list of those a given profile is stalking.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Profile $profile
 | 
			
		||||
     * @param int32 $since
 | 
			
		||||
     * @param int32 $limit
 | 
			
		||||
     * @return Array of URIs
 | 
			
		||||
     */
 | 
			
		||||
    public function generate_following($profile, $since, $limit)
 | 
			
		||||
    {
 | 
			
		||||
        /* Fetch Following */
 | 
			
		||||
        try {
 | 
			
		||||
            $sub = $profile->getSubscribed($since, $limit);
 | 
			
		||||
        } catch (NoResultException $e) {
 | 
			
		||||
            // Just let the exception go on its merry way
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* Get followed' URLs */
 | 
			
		||||
        $subs = [];
 | 
			
		||||
        while ($sub->fetch()) {
 | 
			
		||||
            $subs[] = ActivityPubPlugin::actor_uri($sub);
 | 
			
		||||
        }
 | 
			
		||||
        return $subs;
 | 
			
		||||
                ActivityPubReturn::answer ($res);
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										115
									
								
								actions/apactorinbox.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								actions/apactorinbox.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Actor's Inbox
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class apActorInboxAction extends ManagedAction
 | 
			
		||||
{
 | 
			
		||||
        protected $needLogin = false;
 | 
			
		||||
        protected $canPost   = true;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Handle the Actor Inbox request
 | 
			
		||||
         *
 | 
			
		||||
         * @return void
 | 
			
		||||
         */
 | 
			
		||||
        protected function handle ()
 | 
			
		||||
        {
 | 
			
		||||
                $nickname  = $this->trimmed ('nickname');
 | 
			
		||||
                try {
 | 
			
		||||
                        $user    = User::getByNickname ($nickname);
 | 
			
		||||
                        $profile = $user->getProfile ();
 | 
			
		||||
                        $url     = $profile->profileurl;
 | 
			
		||||
                } catch (Exception $e) {
 | 
			
		||||
                        ActivityPubReturn::error ("Invalid username.");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
 | 
			
		||||
                        ActivityPubReturn::error ("C2S not implemented just yet.");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $data = json_decode (file_get_contents ('php://input'));
 | 
			
		||||
 | 
			
		||||
                // Validate data
 | 
			
		||||
                if (!(isset($data->type))) {
 | 
			
		||||
                        ActivityPubReturn::error ("Type was not specified.");
 | 
			
		||||
                }
 | 
			
		||||
                if (!isset($data->actor)) {
 | 
			
		||||
                        ActivityPubReturn::error ("Actor was not specified.");
 | 
			
		||||
                }
 | 
			
		||||
                if (!isset($data->object)) {
 | 
			
		||||
                        ActivityPubReturn::error ("Object was not specified.");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Get valid Actor object
 | 
			
		||||
                try {
 | 
			
		||||
                        require_once dirname (__DIR__) . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "explorer.php";
 | 
			
		||||
                        $actor_profile = new Activitypub_explorer;
 | 
			
		||||
                        $actor_profile = $actor_profile->lookup ($data->actor);
 | 
			
		||||
                        $actor_profile = $actor_profile[0];
 | 
			
		||||
                } catch (Exception $e) {
 | 
			
		||||
                        ActivityPubReturn::error ("Invalid Actor.", 404);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $to_profiles = array ($user);
 | 
			
		||||
 | 
			
		||||
                // Process request
 | 
			
		||||
                switch ($data->type) {
 | 
			
		||||
                        case "Create":
 | 
			
		||||
                                require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Create.php";
 | 
			
		||||
                                break;
 | 
			
		||||
                        case "Delete":
 | 
			
		||||
                                require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Delete.php";
 | 
			
		||||
                                break;
 | 
			
		||||
                        case "Follow":
 | 
			
		||||
                                require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Follow.php";
 | 
			
		||||
                                break;
 | 
			
		||||
                        case "Like":
 | 
			
		||||
                                require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Like.php";
 | 
			
		||||
                                break;
 | 
			
		||||
                        case "Undo":
 | 
			
		||||
                                require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Undo.php";
 | 
			
		||||
                                break;
 | 
			
		||||
                        case "Announce":
 | 
			
		||||
                                require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Announce.php";
 | 
			
		||||
                                break;
 | 
			
		||||
                        default:
 | 
			
		||||
                                ActivityPubReturn::error ("Invalid type value.");
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,152 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Actor's Liked Collection
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class apActorLikedAction extends ManagedAction
 | 
			
		||||
{
 | 
			
		||||
    protected $needLogin = false;
 | 
			
		||||
    protected $canPost   = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle the Liked Collection request
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    protected function handle()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $profile = Profile::getByID($this->trimmed('id'));
 | 
			
		||||
            $profile_id = $profile->getID();
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            ActivityPubReturn::error('Invalid Actor URI.', 404);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!$profile->isLocal()) {
 | 
			
		||||
            ActivityPubReturn::error("This is not a local user.", 403);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $limit    = intval($this->trimmed('limit'));
 | 
			
		||||
        $since_id = intval($this->trimmed('since_id'));
 | 
			
		||||
        $max_id   = intval($this->trimmed('max_id'));
 | 
			
		||||
 | 
			
		||||
        $limit    = empty($limit) ? 40 : $limit;       // Default is 40
 | 
			
		||||
        $since_id = empty($since_id) ? null : $since_id;
 | 
			
		||||
        $max_id   = empty($max_id) ? null : $max_id;
 | 
			
		||||
 | 
			
		||||
        // Max is 80
 | 
			
		||||
        if ($limit > 80) {
 | 
			
		||||
            $limit = 80;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $fave = $this->fetch_faves($profile_id, $limit, $since_id, $max_id);
 | 
			
		||||
 | 
			
		||||
        $faves = array();
 | 
			
		||||
        while ($fave->fetch()) {
 | 
			
		||||
            $faves[] = $this->pretty_fave(clone ($fave));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $res = [
 | 
			
		||||
            '@context'     => [
 | 
			
		||||
              "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
              "https://w3id.org/security/v1",
 | 
			
		||||
            ],
 | 
			
		||||
            'id'           => common_local_url('apActorLiked', ['id' => $profile_id]),
 | 
			
		||||
            'type'         => 'OrderedCollection',
 | 
			
		||||
            'totalItems'   => Fave::countByProfile($profile),
 | 
			
		||||
            'orderedItems' => $faves
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        ActivityPubReturn::answer($res);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Take a fave object and turns it in a pretty array to be used
 | 
			
		||||
     * as a plugin answer
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Fave $fave_object
 | 
			
		||||
     * @return array pretty array representating a Fave
 | 
			
		||||
     */
 | 
			
		||||
    protected function pretty_fave($fave_object)
 | 
			
		||||
    {
 | 
			
		||||
        $res = [
 | 
			
		||||
            'created' => $fave_object->created,
 | 
			
		||||
            'object' => Activitypub_notice::notice_to_array(Notice::getByID($fave_object->notice_id))
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch faves
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param int32 $user_id
 | 
			
		||||
     * @param int32 $limit
 | 
			
		||||
     * @param int32 $since_id
 | 
			
		||||
     * @param int32 $max_id
 | 
			
		||||
     * @return Fave fetchable fave collection
 | 
			
		||||
     */
 | 
			
		||||
    private static function fetch_faves(
 | 
			
		||||
            $user_id,
 | 
			
		||||
            $limit = 40,
 | 
			
		||||
            $since_id = null,
 | 
			
		||||
            $max_id = null
 | 
			
		||||
        ) {
 | 
			
		||||
        $fav = new Fave();
 | 
			
		||||
 | 
			
		||||
        $fav->user_id = $user_id;
 | 
			
		||||
 | 
			
		||||
        $fav->orderBy('modified DESC');
 | 
			
		||||
 | 
			
		||||
        if ($since_id != null) {
 | 
			
		||||
            $fav->whereAdd("notice_id  > {$since_id}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($max_id != null) {
 | 
			
		||||
            $fav->whereAdd("notice_id  < {$max_id}");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $fav->limit($limit);
 | 
			
		||||
 | 
			
		||||
        $fav->find();
 | 
			
		||||
 | 
			
		||||
        return $fav;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										146
									
								
								actions/apactorlikedcollection.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								actions/apactorlikedcollection.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Actor's Liked Collection
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class apActorLikedCollectionAction extends ManagedAction
 | 
			
		||||
{
 | 
			
		||||
        protected $needLogin = false;
 | 
			
		||||
        protected $canPost   = true;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Handle the Liked Collection request
 | 
			
		||||
         *
 | 
			
		||||
         * @return void
 | 
			
		||||
         */
 | 
			
		||||
        protected function handle ()
 | 
			
		||||
        {
 | 
			
		||||
                $nickname = $this->trimmed ('nickname');
 | 
			
		||||
                try {
 | 
			
		||||
                        $user    = User::getByNickname ($nickname);
 | 
			
		||||
                        $profile = $user->getProfile ();
 | 
			
		||||
                        $url     = $profile->profileurl;
 | 
			
		||||
                } catch (Exception $e) {
 | 
			
		||||
                        ActivityPubReturn::error ('Invalid username.');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $limit    = intval ($this->trimmed ('limit'));
 | 
			
		||||
                $since_id = intval ($this->trimmed ('since_id'));
 | 
			
		||||
                $max_id   = intval ($this->trimmed ('max_id'));
 | 
			
		||||
 | 
			
		||||
                $limit    = empty ($limit) ? 40 : $limit;       // Default is 40
 | 
			
		||||
                $since_id = empty ($since_id) ? null : $since_id;
 | 
			
		||||
                $max_id   = empty ($max_id) ? null : $max_id;
 | 
			
		||||
 | 
			
		||||
                // Max is 80
 | 
			
		||||
                if ($limit > 80) {
 | 
			
		||||
                        $limit = 80;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $fave = $this->fetch_faves($user->getID(), $limit, $since_id, $max_id);
 | 
			
		||||
 | 
			
		||||
                $faves = array();
 | 
			
		||||
                while ($fave->fetch ()) {
 | 
			
		||||
                        $faves[] = $this->pretty_fave (clone ($fave));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $res = [
 | 
			
		||||
                  '@context'          => [
 | 
			
		||||
                    "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                    [
 | 
			
		||||
                      "@language" => "en"
 | 
			
		||||
                    ]
 | 
			
		||||
                  ],
 | 
			
		||||
                  'id'           => "{$url}/liked.json",
 | 
			
		||||
                  'type'         => 'OrderedCollection',
 | 
			
		||||
                  'totalItems'   => Fave::countByProfile ($profile),
 | 
			
		||||
                  'orderedItems' => $faves
 | 
			
		||||
                ];
 | 
			
		||||
 | 
			
		||||
                ActivityPubReturn::answer ($res);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Take a fave object and turns it in a pretty array to be used
 | 
			
		||||
         * as a plugin answer
 | 
			
		||||
         *
 | 
			
		||||
         * @param Fave $fave_object
 | 
			
		||||
         * @return array pretty array representating a Fave
 | 
			
		||||
         */
 | 
			
		||||
        protected function pretty_fave ($fave_object)
 | 
			
		||||
        {
 | 
			
		||||
                $res = array("uri" => $fave_object->uri,
 | 
			
		||||
                             "created" => $fave_object->created,
 | 
			
		||||
                             "object" => Activitypub_notice::notice_to_array (Notice::getByID ($fave_object->notice_id)));
 | 
			
		||||
 | 
			
		||||
                return $res;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Fetch faves
 | 
			
		||||
         *
 | 
			
		||||
         * @param int32 $user_id
 | 
			
		||||
         * @param int32 $limit
 | 
			
		||||
         * @param int32 $since_id
 | 
			
		||||
         * @param int32 $max_id
 | 
			
		||||
         * @return Fave fetchable fave collection
 | 
			
		||||
         */
 | 
			
		||||
        private static function fetch_faves ($user_id, $limit = 40, $since_id = null,
 | 
			
		||||
                                             $max_id = null)
 | 
			
		||||
        {
 | 
			
		||||
                $fav = new Fave ();
 | 
			
		||||
 | 
			
		||||
                $fav->user_id = $user_id;
 | 
			
		||||
 | 
			
		||||
                $fav->orderBy ('modified DESC');
 | 
			
		||||
 | 
			
		||||
                if ($since_id != null) {
 | 
			
		||||
                        $fav->whereAdd ("notice_id  > {$since_id}");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($max_id != null) {
 | 
			
		||||
                        $fav->whereAdd ("notice_id  < {$max_id}");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $fav->limit ($limit);
 | 
			
		||||
 | 
			
		||||
                $fav->find ();
 | 
			
		||||
 | 
			
		||||
                return $fav;
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,134 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Inbox Request Handler
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class apActorOutboxAction extends ManagedAction
 | 
			
		||||
{
 | 
			
		||||
    protected $needLogin = false;
 | 
			
		||||
    protected $canPost   = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle the Outbox request
 | 
			
		||||
     *
 | 
			
		||||
     * @author Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
     */
 | 
			
		||||
    protected function handle()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $profile = Profile::getByID($this->trimmed('id'));
 | 
			
		||||
            $profile_id = $profile->getID();
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            ActivityPubReturn::error('Invalid Actor URI.', 404);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!$profile->isLocal()) {
 | 
			
		||||
            ActivityPubReturn::error("This is not a local user.", 403);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!isset($_GET["page"])) {
 | 
			
		||||
            $page = 0;
 | 
			
		||||
        } else {
 | 
			
		||||
            $page = intval($this->trimmed('page'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($page < 0) {
 | 
			
		||||
            ActivityPubReturn::error('Invalid page number.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $since = ($page - 1) * PROFILES_PER_MINILIST;
 | 
			
		||||
        $limit = (($page - 1) == 0 ? 1 : $page) * PROFILES_PER_MINILIST;
 | 
			
		||||
 | 
			
		||||
        /* Calculate total items */
 | 
			
		||||
        $total_notes = $profile->noticeCount();
 | 
			
		||||
        $total_pages = ceil($total_notes / PROFILES_PER_MINILIST);
 | 
			
		||||
 | 
			
		||||
        $res = [
 | 
			
		||||
            '@context'     => [
 | 
			
		||||
              "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
              "https://w3id.org/security/v1",
 | 
			
		||||
            ],
 | 
			
		||||
            'id'           => common_local_url('apActorOutbox', ['id' => $profile_id]).(($page != 0) ? '?page='.$page : ''),
 | 
			
		||||
            'type'         => ($page == 0 ? 'OrderedCollection' : 'OrderedCollectionPage'),
 | 
			
		||||
            'totalItems'   => $total_notes
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        if ($page == 0) {
 | 
			
		||||
            $res['first'] = common_local_url('apActorOutbox', ['id' => $profile_id]).'?page=1';
 | 
			
		||||
        } else {
 | 
			
		||||
            $res['orderedItems'] = $this->generate_outbox($profile);
 | 
			
		||||
            $res['partOf'] = common_local_url('apActorOutbox', ['id' => $profile_id]);
 | 
			
		||||
 | 
			
		||||
            if ($page+1 < $total_pages) {
 | 
			
		||||
                $res['next'] = common_local_url('apActorOutbox', ['id' => $profile_id]).'page='.($page+1 == 1 ? 2 : $page+1);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ($page > 1) {
 | 
			
		||||
                $res['prev'] = common_local_url('apActorOutbox', ['id' => $profile_id]).'?page='.($page-1 <= 0 ? 1 : $page-1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ActivityPubReturn::answer($res);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates a list of people following given profile.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
     * @param Profile $profile
 | 
			
		||||
     * @return Array of Notices
 | 
			
		||||
     */
 | 
			
		||||
    public function generate_outbox($profile)
 | 
			
		||||
    {
 | 
			
		||||
        /* Fetch Notices */
 | 
			
		||||
        $notices = [];
 | 
			
		||||
        $notice = $profile->getNotices();
 | 
			
		||||
        while ($notice->fetch()) {
 | 
			
		||||
            $note = $notice;
 | 
			
		||||
 | 
			
		||||
            // TODO: Handle other types
 | 
			
		||||
            if ($note->object_type == 'http://activitystrea.ms/schema/1.0/note') {
 | 
			
		||||
                $notices[] = Activitypub_create::create_to_array(
 | 
			
		||||
                    ActivityPubPlugin::actor_uri($note->getProfile()),
 | 
			
		||||
                    Activitypub_notice::notice_to_array($note)
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $notices;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								actions/apactorprofile.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										31
									
								
								actions/apactorprofile.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -19,13 +19,14 @@
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -33,6 +34,7 @@ if (!defined('GNUSOCIAL')) {
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
@@ -45,32 +47,21 @@ class apActorProfileAction extends ManagedAction
 | 
			
		||||
        /**
 | 
			
		||||
         * Handle the Actor Profile request
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @return void
 | 
			
		||||
         */
 | 
			
		||||
        protected function handle()
 | 
			
		||||
        {
 | 
			
		||||
        if (!empty($id = $this->trimmed('id'))) {
 | 
			
		||||
                $nickname = $this->trimmed ('nickname');
 | 
			
		||||
                try {
 | 
			
		||||
                $profile = Profile::getByID($id);
 | 
			
		||||
            } catch (Exception $e) {
 | 
			
		||||
                ActivityPubReturn::error('Invalid Actor URI.', 404);
 | 
			
		||||
            }
 | 
			
		||||
            unset($id);
 | 
			
		||||
        } else {
 | 
			
		||||
            try {
 | 
			
		||||
                $profile = User::getByNickname($this->trimmed('nickname'))->getProfile();
 | 
			
		||||
            } catch (Exception $e) {
 | 
			
		||||
                ActivityPubReturn::error('Invalid username.', 404);
 | 
			
		||||
                        $user    = User::getByNickname ($nickname);
 | 
			
		||||
                        $profile = $user->getProfile ();
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception $e) {
 | 
			
		||||
                        ActivityPubReturn::error ('Invalid username.', 404);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        if (!$profile->isLocal()) {
 | 
			
		||||
            ActivityPubReturn::error("This is not a local user.", 403);
 | 
			
		||||
        }
 | 
			
		||||
                $res = Activitypub_profile::profile_to_array ($profile);
 | 
			
		||||
 | 
			
		||||
        $res = Activitypub_profile::profile_to_array($profile);
 | 
			
		||||
 | 
			
		||||
        ActivityPubReturn::answer($res);
 | 
			
		||||
                ActivityPubReturn::answer ($res);
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										342
									
								
								actions/apfeed.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								actions/apfeed.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,342 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
        exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub Feed
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class apFeedAction extends ManagedAction
 | 
			
		||||
{
 | 
			
		||||
        protected $needLogin = false;
 | 
			
		||||
        protected $canPost = true;
 | 
			
		||||
 | 
			
		||||
        var $page = 1;
 | 
			
		||||
        var $count = 20;
 | 
			
		||||
        var $max_id = 0;
 | 
			
		||||
        var $since_id = 0;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Handle the Actor Inbox request
 | 
			
		||||
         *
 | 
			
		||||
         * @return void
 | 
			
		||||
         */
 | 
			
		||||
        protected function handle ()
 | 
			
		||||
        {
 | 
			
		||||
                $this->showJsonTimeline($this->getNotices());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get notices
 | 
			
		||||
         *
 | 
			
		||||
         * @return array notices
 | 
			
		||||
         */
 | 
			
		||||
         function getNotices()
 | 
			
		||||
         {
 | 
			
		||||
                $notices = array ();
 | 
			
		||||
 | 
			
		||||
                $stream = new PublicNoticeStream (null);
 | 
			
		||||
 | 
			
		||||
                $notice = $stream->getNotices (($this->page - 1) * $this->count,
 | 
			
		||||
                                              $this->count,
 | 
			
		||||
                                              $this->since_id, $this->max_id);
 | 
			
		||||
 | 
			
		||||
                $notices = $notice->fetchAll ();
 | 
			
		||||
 | 
			
		||||
                NoticeList::prefill ($notices);
 | 
			
		||||
 | 
			
		||||
                return $notices;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function showJsonTimeline ($notice)
 | 
			
		||||
        {
 | 
			
		||||
                header ('Content-Type: application/json; charset=utf-8');
 | 
			
		||||
 | 
			
		||||
                $statuses = array ();
 | 
			
		||||
 | 
			
		||||
                if (is_array ($notice)) {
 | 
			
		||||
                        //FIXME: make everything calling showJsonTimeline use only Notice objects
 | 
			
		||||
                        $ids = array ();
 | 
			
		||||
                        foreach($notice as $n) {
 | 
			
		||||
                                $ids[] = $n->getID ();
 | 
			
		||||
                        }
 | 
			
		||||
                        $notice = Notice::multiGet ('id', $ids);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                while ($notice->fetch ()) {
 | 
			
		||||
                        try {
 | 
			
		||||
                                $twitter_status = $this->twitterStatusArray ($notice);
 | 
			
		||||
                                array_push ($statuses, $twitter_status);
 | 
			
		||||
                        } catch (Exception $e) {
 | 
			
		||||
                                common_log (LOG_ERR, $e->getMessage ());
 | 
			
		||||
                                continue;
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $this->showJsonObjects ($statuses);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function showJsonObjects ($objects)
 | 
			
		||||
        {
 | 
			
		||||
                $json_objects = json_encode ($objects);
 | 
			
		||||
                if ($json_objects === false) {
 | 
			
		||||
                        $this-> clientError(_('JSON encoding failed. Error: ').json_last_error_msg ());
 | 
			
		||||
                } else {
 | 
			
		||||
                        print $json_objects;
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function twitterStatusArray ($notice, $include_user = true)
 | 
			
		||||
        {
 | 
			
		||||
                $base = $this->twitterSimpleStatusArray ($notice, $include_user);
 | 
			
		||||
 | 
			
		||||
                // FIXME: MOVE TO SHARE PLUGIN
 | 
			
		||||
                if (!empty ($notice->repeat_of)) {
 | 
			
		||||
                        $original = Notice::getKV ('id', $notice->repeat_of);
 | 
			
		||||
                        if ($original instanceof Notice) {
 | 
			
		||||
                                $orig_array = $this->twitterSimpleStatusArray($original, $include_user);
 | 
			
		||||
                                $base['retweeted_status'] = $orig_array;
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return $base;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function twitterSimpleStatusArray ($notice, $include_user = true)
 | 
			
		||||
        {
 | 
			
		||||
                $profile = $notice->getProfile ();
 | 
			
		||||
 | 
			
		||||
                $twitter_status = array ();
 | 
			
		||||
                $twitter_status['text'] = $notice->content;
 | 
			
		||||
                $twitter_status['truncated'] = false;
 | 
			
		||||
                $twitter_status['created_at'] = self::dateTwitter ($notice->created);
 | 
			
		||||
                try {
 | 
			
		||||
                        // We could just do $notice->reply_to but maybe the future holds a
 | 
			
		||||
                        // different story for parenting.
 | 
			
		||||
                        $parent = $notice->getParent ();
 | 
			
		||||
                        $in_reply_to = $parent->id;
 | 
			
		||||
                } catch (NoParentNoticeException $e) {
 | 
			
		||||
                        $in_reply_to = null;
 | 
			
		||||
                } catch (NoResultException $e) {
 | 
			
		||||
                        // the in_reply_to message has probably been deleted
 | 
			
		||||
                        $in_reply_to = null;
 | 
			
		||||
                }
 | 
			
		||||
                $twitter_status['in_reply_to_status_id'] = $in_reply_to;
 | 
			
		||||
 | 
			
		||||
                $source = null;
 | 
			
		||||
                $source_link = null;
 | 
			
		||||
 | 
			
		||||
                $ns = $notice->getSource ();
 | 
			
		||||
                if ($ns instanceof Notice_source) {
 | 
			
		||||
                        $source = $ns->code;
 | 
			
		||||
                        if (!empty ($ns->url)) {
 | 
			
		||||
                                $source_link = $ns->url;
 | 
			
		||||
                                if (!empty ($ns->name)) {
 | 
			
		||||
                                        $source = $ns->name;
 | 
			
		||||
                                }
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $twitter_status['uri'] = $notice->getUri ();
 | 
			
		||||
                $twitter_status['source'] = $source;
 | 
			
		||||
                $twitter_status['source_link'] = $source_link;
 | 
			
		||||
                $twitter_status['id'] = intval ($notice->id);
 | 
			
		||||
 | 
			
		||||
                $replier_profile = null;
 | 
			
		||||
 | 
			
		||||
                if ($notice->reply_to) {
 | 
			
		||||
                        $reply = Notice::getKV (intval ($notice->reply_to));
 | 
			
		||||
                        if ($reply) {
 | 
			
		||||
                                $replier_profile = $reply->getProfile ();
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $twitter_status['in_reply_to_user_id'] =
 | 
			
		||||
                    ($replier_profile) ? intval ($replier_profile->id) : null;
 | 
			
		||||
                $twitter_status['in_reply_to_screen_name'] =
 | 
			
		||||
                    ($replier_profile) ? $replier_profile->nickname : null;
 | 
			
		||||
 | 
			
		||||
                try {
 | 
			
		||||
                        $notloc = Notice_location::locFromStored ($notice);
 | 
			
		||||
                        // This is the format that GeoJSON expects stuff to be in
 | 
			
		||||
                        $twitter_status['geo'] = array ('type' => 'Point',
 | 
			
		||||
                                                        'coordinates' => array (
 | 
			
		||||
                                                            (float) $notloc->lat,
 | 
			
		||||
                                                            (float) $notloc->lon));
 | 
			
		||||
                } catch (ServerException $e) {
 | 
			
		||||
                        $twitter_status['geo'] = null;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Enclosures
 | 
			
		||||
                $attachments = $notice->attachments ();
 | 
			
		||||
 | 
			
		||||
                if (!empty ($attachments)) {
 | 
			
		||||
 | 
			
		||||
                        $twitter_status['attachments'] = array ();
 | 
			
		||||
 | 
			
		||||
                        foreach ($attachments as $attachment) {
 | 
			
		||||
                                try {
 | 
			
		||||
                                        $enclosure_o = $attachment->getEnclosure ();
 | 
			
		||||
                                        $enclosure = array();
 | 
			
		||||
                                        $enclosure['url'] = $enclosure_o->url;
 | 
			
		||||
                                        $enclosure['mimetype'] = $enclosure_o->mimetype;
 | 
			
		||||
                                        $enclosure['size'] = $enclosure_o->size;
 | 
			
		||||
                                        $twitter_status['attachments'][] = $enclosure;
 | 
			
		||||
                                } catch (ServerException $e) {
 | 
			
		||||
                                        // There was not enough metadata available
 | 
			
		||||
                                }
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($include_user && $profile) {
 | 
			
		||||
                        // Don't get notice (recursive!)
 | 
			
		||||
                        $twitter_user = $this->twitterUserArray($profile, false);
 | 
			
		||||
                        $twitter_status['user'] = $twitter_user;
 | 
			
		||||
                }
 | 
			
		||||
                // StatusNet-specific
 | 
			
		||||
 | 
			
		||||
                $twitter_status['statusnet_html'] = $notice->getRendered();
 | 
			
		||||
                $twitter_status['statusnet_conversation_id'] = intval($notice->conversation);
 | 
			
		||||
 | 
			
		||||
                // The event call to handle NoticeSimpleStatusArray lets plugins add data to the output array
 | 
			
		||||
                Event::handle('NoticeSimpleStatusArray',
 | 
			
		||||
                              array ($notice, &$twitter_status, $this->scoped,
 | 
			
		||||
                                    array ('include_user' => $include_user)));
 | 
			
		||||
 | 
			
		||||
                return $twitter_status;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static function dateTwitter($dt)
 | 
			
		||||
        {
 | 
			
		||||
                $dateStr = date ('d F Y H:i:s', strtotime ($dt));
 | 
			
		||||
                $d = new DateTime ($dateStr, new DateTimeZone ('UTC'));
 | 
			
		||||
                $d->setTimezone (new DateTimeZone(common_timezone()));
 | 
			
		||||
                return $d->format ('D M d H:i:s O Y');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function twitterUserArray ($profile, $get_notice = false)
 | 
			
		||||
        {
 | 
			
		||||
                $twitter_user = array ();
 | 
			
		||||
 | 
			
		||||
                try {
 | 
			
		||||
                        $user = $profile->getUser ();
 | 
			
		||||
                } catch (NoSuchUserException $e) {
 | 
			
		||||
                        $user = null;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $twitter_user['id'] = $profile->getID ();
 | 
			
		||||
                $twitter_user['name'] = $profile->getBestName ();
 | 
			
		||||
                $twitter_user['screen_name'] = $profile->getNickname ();
 | 
			
		||||
                $twitter_user['location'] = $profile->location;
 | 
			
		||||
                $twitter_user['description'] = $profile->getDescription ();
 | 
			
		||||
 | 
			
		||||
                // TODO: avatar url template (example.com/user/avatar?size={x}x{y})
 | 
			
		||||
                $twitter_user['profile_image_url'] = Avatar::urlByProfile($profile, AVATAR_STREAM_SIZE);
 | 
			
		||||
                $twitter_user['profile_image_url_https'] = $twitter_user['profile_image_url'];
 | 
			
		||||
 | 
			
		||||
                // START introduced by qvitter API, not necessary for StatusNet API
 | 
			
		||||
                $twitter_user['profile_image_url_profile_size'] = Avatar::urlByProfile($profile, AVATAR_PROFILE_SIZE);
 | 
			
		||||
                try {
 | 
			
		||||
                        $avatar = Avatar::getUploaded ($profile);
 | 
			
		||||
                        $origurl = $avatar->displayUrl ();
 | 
			
		||||
                } catch(Exception $e) {
 | 
			
		||||
                        $origurl = $twitter_user['profile_image_url_profile_size'];
 | 
			
		||||
                }
 | 
			
		||||
                $twitter_user['profile_image_url_original'] = $origurl;
 | 
			
		||||
 | 
			
		||||
                $twitter_user['groups_count'] = $profile->getGroupCount();
 | 
			
		||||
                foreach (array('linkcolor', 'backgroundcolor') as $key) {
 | 
			
		||||
                        $twitter_user[$key] =  Profile_prefs::getConfigData($profile, 'theme', $key);
 | 
			
		||||
                }
 | 
			
		||||
                // END introduced by qvitter API, not necessary for StatusNet API
 | 
			
		||||
 | 
			
		||||
                $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
 | 
			
		||||
                $twitter_user['protected'] = (!empty($user) && $user->private_stream) ? true : false;
 | 
			
		||||
                $twitter_user['followers_count'] = $profile->subscriberCount();
 | 
			
		||||
 | 
			
		||||
                // Note: some profiles don't have an associated user
 | 
			
		||||
 | 
			
		||||
                $twitter_user['friends_count'] = $profile->subscriptionCount ();
 | 
			
		||||
 | 
			
		||||
                $twitter_user['created_at'] = self::dateTwitter($profile->created);
 | 
			
		||||
 | 
			
		||||
                $timezone = 'UTC';
 | 
			
		||||
 | 
			
		||||
                if (!empty($user) && $user->timezone) {
 | 
			
		||||
                        $timezone = $user->timezone;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $t = new DateTime;
 | 
			
		||||
                $t->setTimezone (new DateTimeZone ($timezone));
 | 
			
		||||
 | 
			
		||||
                $twitter_user['utc_offset']     = $t->format('Z');
 | 
			
		||||
                $twitter_user['time_zone']      = $timezone;
 | 
			
		||||
                $twitter_user['statuses_count'] = $profile->noticeCount();
 | 
			
		||||
 | 
			
		||||
                // Is the requesting user following this user?
 | 
			
		||||
                // These values might actually also mean "unknown". Ambiguity issues?
 | 
			
		||||
                $twitter_user['following']          = false;
 | 
			
		||||
                $twitter_user['statusnet_blocking'] = false;
 | 
			
		||||
                $twitter_user['notifications']      = false;
 | 
			
		||||
 | 
			
		||||
                if ($this->scoped instanceof Profile) {
 | 
			
		||||
                        try {
 | 
			
		||||
                                $sub = Subscription::getSubscription($this->scoped, $profile);
 | 
			
		||||
                                // Notifications on?
 | 
			
		||||
                                $twitter_user['following'] = true;
 | 
			
		||||
                                $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
 | 
			
		||||
                        } catch (NoResultException $e) {
 | 
			
		||||
                                // well, the values are already false...
 | 
			
		||||
                        }
 | 
			
		||||
                        $twitter_user['statusnet_blocking'] = $this->scoped->hasBlocked($profile);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($get_notice) {
 | 
			
		||||
                        $notice = $profile->getCurrentNotice ();
 | 
			
		||||
                        if ($notice instanceof Notice) {
 | 
			
		||||
                                // don't get user!
 | 
			
		||||
                                $twitter_user['status'] = $this->twitterStatusArray($notice, false);
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
                // StatusNet-specific
 | 
			
		||||
 | 
			
		||||
                $twitter_user['statusnet_profile_url'] = $profile->profileurl;
 | 
			
		||||
 | 
			
		||||
                // The event call to handle NoticeSimpleStatusArray lets plugins add data to the output array
 | 
			
		||||
                Event::handle('TwitterUserArray', array ($profile, &$twitter_user, $this->scoped, array()));
 | 
			
		||||
 | 
			
		||||
                return $twitter_user;
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,103 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Inbox Request Handler
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class apInboxAction extends ManagedAction
 | 
			
		||||
{
 | 
			
		||||
    protected $needLogin = false;
 | 
			
		||||
    protected $canPost   = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle the Inbox request
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    protected function handle()
 | 
			
		||||
    {
 | 
			
		||||
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
 | 
			
		||||
            ActivityPubReturn::error('Only POST requests allowed.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        common_debug('ActivityPub Inbox: Received a POST request.');
 | 
			
		||||
        $data = file_get_contents('php://input');
 | 
			
		||||
        common_debug('ActivityPub Inbox: Request contents: '.$data);
 | 
			
		||||
        $data = json_decode(file_get_contents('php://input'), true);
 | 
			
		||||
 | 
			
		||||
        if (!isset($data['actor'])) {
 | 
			
		||||
            ActivityPubReturn::error('Actor not found in the request.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $actor = ActivityPub_explorer::get_profile_from_url($data['actor']);
 | 
			
		||||
        $actor_public_key = new Activitypub_rsa();
 | 
			
		||||
        $actor_public_key = $actor_public_key->ensure_public_key($actor);
 | 
			
		||||
 | 
			
		||||
        common_debug('ActivityPub Inbox: HTTP Signature: Validation will now start!');
 | 
			
		||||
 | 
			
		||||
        $headers = $this->get_all_headers();
 | 
			
		||||
        common_debug('ActivityPub Inbox: Request Headers: '.print_r($headers, true));
 | 
			
		||||
 | 
			
		||||
        // TODO: Validate HTTP Signature, if it fails, attempt once with profile update
 | 
			
		||||
 | 
			
		||||
        common_debug('ActivityPub Inbox: HTTP Signature: Authorized request. Will now start the inbox handler.');
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            new Activitypub_inbox_handler($data, $actor);
 | 
			
		||||
            ActivityPubReturn::answer();
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            ActivityPubReturn::error($e->getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all HTTP header key/values as an associative array for the current request.
 | 
			
		||||
     *
 | 
			
		||||
     * @author PHP Manual Contributed Notes <joyview@gmail.com>
 | 
			
		||||
     * @return string[string] The HTTP header key/value pairs.
 | 
			
		||||
     */
 | 
			
		||||
    private function get_all_headers()
 | 
			
		||||
    {
 | 
			
		||||
        $headers = [];
 | 
			
		||||
        foreach ($_SERVER as $name => $value) {
 | 
			
		||||
            if (substr($name, 0, 5) == 'HTTP_') {
 | 
			
		||||
                $headers[strtolower(str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))))] = $value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $headers;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										135
									
								
								actions/apsharedinbox.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								actions/apsharedinbox.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
			
		||||
<?php
 | 
			
		||||
require_once dirname (__DIR__) . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "explorer.php";
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Shared Inbox Handler
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class apSharedInboxAction extends ManagedAction
 | 
			
		||||
{
 | 
			
		||||
        protected $needLogin = false;
 | 
			
		||||
        protected $canPost   = true;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Handle the Shared Inbox request
 | 
			
		||||
         *
 | 
			
		||||
         * @return void
 | 
			
		||||
         */
 | 
			
		||||
        protected function handle ()
 | 
			
		||||
        {
 | 
			
		||||
                if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
 | 
			
		||||
                        ActivityPubReturn::error ("Only POST requests allowed.");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $data = json_decode (file_get_contents ('php://input'));
 | 
			
		||||
 | 
			
		||||
                // Validate data
 | 
			
		||||
                if (!isset($data->type)) {
 | 
			
		||||
                        ActivityPubReturn::error ("Type was not specified.");
 | 
			
		||||
                }
 | 
			
		||||
                if (!isset($data->actor)) {
 | 
			
		||||
                        ActivityPubReturn::error ("Actor was not specified.");
 | 
			
		||||
                }
 | 
			
		||||
                if (!isset($data->object)) {
 | 
			
		||||
                        ActivityPubReturn::error ("Object was not specified.");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $discovery = new Activitypub_explorer;
 | 
			
		||||
 | 
			
		||||
                // Get valid Actor object
 | 
			
		||||
                try {
 | 
			
		||||
                        $actor_profile = $discovery->lookup ($data->actor);
 | 
			
		||||
                        $actor_profile = $actor_profile[0];
 | 
			
		||||
                } catch (Exception $e) {
 | 
			
		||||
                        ActivityPubReturn::error ("Invalid Actor.", 404);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                unset ($discovery);
 | 
			
		||||
 | 
			
		||||
                // Public To:
 | 
			
		||||
                $public_to = array ("https://www.w3.org/ns/activitystreams#Public",
 | 
			
		||||
                                    "Public",
 | 
			
		||||
                                    "as:Public");
 | 
			
		||||
 | 
			
		||||
                // Process request
 | 
			
		||||
                switch ($data->type) {
 | 
			
		||||
                        case "Create":
 | 
			
		||||
                                if (!isset($data->to)) {
 | 
			
		||||
                                        ActivityPubReturn::error ("To was not specified.");
 | 
			
		||||
                                }
 | 
			
		||||
                                $discovery = new Activitypub_Discovery;
 | 
			
		||||
                                $to_profiles = array ();
 | 
			
		||||
                                // Generate To objects
 | 
			
		||||
                                if (is_array ($data->to)) {
 | 
			
		||||
                                        // Remove duplicates from To actors set
 | 
			
		||||
                                        array_unique ($data->to);
 | 
			
		||||
                                        foreach ($data->to as $to_url) {
 | 
			
		||||
                                                try {
 | 
			
		||||
                                                        $to_profiles = array_merge ($to_profiles, $discovery->lookup ($to_url));
 | 
			
		||||
                                                } catch (Exception $e) {
 | 
			
		||||
                                                        // XXX: Invalid actor found, not sure how we handle those
 | 
			
		||||
                                                }
 | 
			
		||||
                                        }
 | 
			
		||||
                                } else if (empty ($data->to) || in_array ($data->to, $public_to)) {
 | 
			
		||||
                                        // No need to do anything else at this point, let's just break out the if
 | 
			
		||||
                                } else {
 | 
			
		||||
                                        try {
 | 
			
		||||
                                                $to_profiles[]= $discovery->lookup ($data->to);
 | 
			
		||||
                                        } catch (Exception $e) {
 | 
			
		||||
                                                ActivityPubReturn::error ("Invalid Actor.", 404);
 | 
			
		||||
                                        }
 | 
			
		||||
                                }
 | 
			
		||||
                                unset ($discovery);
 | 
			
		||||
                                require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Create.php";
 | 
			
		||||
                                break;
 | 
			
		||||
                        case "Follow":
 | 
			
		||||
                                require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Follow.php";
 | 
			
		||||
                                break;
 | 
			
		||||
                        case "Like":
 | 
			
		||||
                                require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Like.php";
 | 
			
		||||
                                break;
 | 
			
		||||
                        case "Announce":
 | 
			
		||||
                                require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Announce.php";
 | 
			
		||||
                                break;
 | 
			
		||||
                        case "Undo":
 | 
			
		||||
                                require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Undo.php";
 | 
			
		||||
                                break;
 | 
			
		||||
                        default:
 | 
			
		||||
                                ActivityPubReturn::error ("Invalid type value.");
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								classes/Activitypub_reject.php → actions/inbox/Announce.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										37
									
								
								classes/Activitypub_reject.php → actions/inbox/Announce.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -20,39 +20,18 @@
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub error representation
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_reject extends Managed_DataObject
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates an ActivityPub representation of a Reject
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param array $object
 | 
			
		||||
     * @return pretty array to be used in a response
 | 
			
		||||
     */
 | 
			
		||||
    public static function reject_to_array($object)
 | 
			
		||||
    {
 | 
			
		||||
        $res = [
 | 
			
		||||
                '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
                "type"   => "Reject",
 | 
			
		||||
                "object" => $object
 | 
			
		||||
        ];
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
try {
 | 
			
		||||
        Notice::getByUri ($data->object)->repeat ($actor_profile, "ActivityPub");
 | 
			
		||||
        ActivityPubReturn::answer ("Notice repeated successfully.");
 | 
			
		||||
} catch (Exception $e) {
 | 
			
		||||
        ActivityPubReturn::error ($e->getMessage (), 403);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										93
									
								
								actions/inbox/Create.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								actions/inbox/Create.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$valid_object_types = array ("Note");
 | 
			
		||||
 | 
			
		||||
// Validate data
 | 
			
		||||
if (!(isset ($data->object->type) && in_array ($data->object->type, $valid_object_types))) {
 | 
			
		||||
        ActivityPubReturn::error ("Invalid Object type.");
 | 
			
		||||
}
 | 
			
		||||
if (!isset ($data->object->content)) {
 | 
			
		||||
        ActivityPubReturn::error ("Object content was not specified.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$content = $data->object->content;
 | 
			
		||||
 | 
			
		||||
$act = new Activity ();
 | 
			
		||||
$act->verb = ActivityVerb::POST;
 | 
			
		||||
$act->time = time ();
 | 
			
		||||
$act->actor = $actor_profile->asActivityObject ();
 | 
			
		||||
 | 
			
		||||
$act->context = new ActivityContext ();
 | 
			
		||||
 | 
			
		||||
// Is this a reply?
 | 
			
		||||
if (isset ($data->object->reply_to)) {
 | 
			
		||||
        $reply_to = Notice::getByUri ($data->object->reply_to);
 | 
			
		||||
        $act->context->replyToID = $reply_to->getUri ();
 | 
			
		||||
        $act->context->replyToUrl = $data->object->reply_to;
 | 
			
		||||
} else {
 | 
			
		||||
        $reply_to = null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$act->context->attention = common_get_attentions ($content, $actor_profile, $reply_to);
 | 
			
		||||
 | 
			
		||||
foreach ($to_profiles as $to)
 | 
			
		||||
{
 | 
			
		||||
        $act->context->attention[$to->getUri ()] = "http://activitystrea.ms/schema/1.0/person";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Reject notice if it is too long (without the HTML)
 | 
			
		||||
// This is done after MediaFile::fromUpload etc. just to act the same as the ApiStatusesUpdateAction
 | 
			
		||||
if (Notice::contentTooLong ($content)) {
 | 
			
		||||
        ActivityPubReturn::error ("That's too long. Maximum notice size is %d character.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$options = array ('source' => 'ActivityPub', 'uri' => $data->id);
 | 
			
		||||
// $options gets filled with possible scoping settings
 | 
			
		||||
ToSelector::fillActivity ($this, $act, $options);
 | 
			
		||||
 | 
			
		||||
$actobj = new ActivityObject ();
 | 
			
		||||
$actobj->type = ActivityObject::NOTE;
 | 
			
		||||
$actobj->content = common_render_content ($content, $actor_profile, $reply_to);
 | 
			
		||||
 | 
			
		||||
// Finally add the activity object to our activity
 | 
			
		||||
$act->objects[] = $actobj;
 | 
			
		||||
 | 
			
		||||
try {
 | 
			
		||||
        $res = array ("@context" => "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                  "id"     => $data->id,
 | 
			
		||||
                  "type"   => "Create",
 | 
			
		||||
                  "actor"  => $data->actor,
 | 
			
		||||
                  "object" => Activitypub_notice::notice_to_array (Notice::saveActivity ($act, $actor_profile, $options)));
 | 
			
		||||
        ActivityPubReturn::answer ($res);
 | 
			
		||||
} catch (Exception $e) {
 | 
			
		||||
        ActivityPubReturn::error ($e->getMessage ());
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								classes/Activitypub_announce.php → actions/inbox/Delete.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										42
									
								
								classes/Activitypub_announce.php → actions/inbox/Delete.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -20,40 +20,22 @@
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub error representation
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_announce extends Managed_DataObject
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates an ActivityPub representation of a Announce
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param array $object
 | 
			
		||||
     * @return pretty array to be used in a response
 | 
			
		||||
     */
 | 
			
		||||
    public static function announce_to_array($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        $res = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
            "type"   => "Announce",
 | 
			
		||||
            "actor"  => $actor,
 | 
			
		||||
            "object" => $object
 | 
			
		||||
        ];
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
try {
 | 
			
		||||
        Activitypub_notice::getByUri ($data->object)->deleteAs ($actor_profile);
 | 
			
		||||
        $res = array ("@context" => "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                  "type"   => "Delete",
 | 
			
		||||
                  "actor"  => $data->actor,
 | 
			
		||||
                  "object" => $data->object);
 | 
			
		||||
        ActivityPubReturn::answer ($res);
 | 
			
		||||
} catch (Exception $e) {
 | 
			
		||||
        ActivityPubReturn::error ($e->getMessage (), 403);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										67
									
								
								actions/apnotice.php → actions/inbox/Follow.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										67
									
								
								actions/apnotice.php → actions/inbox/Follow.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -20,48 +20,39 @@
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Notice (Local notices only)
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class apNoticeAction extends ManagedAction
 | 
			
		||||
{
 | 
			
		||||
    protected $needLogin = false;
 | 
			
		||||
    protected $canPost   = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle the Notice request
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    protected function handle()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $notice = Notice::getByID($this->trimmed('id'));
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            ActivityPubReturn::error('Invalid Notice URI.', 404);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!$notice->isLocal()) {
 | 
			
		||||
            ActivityPubReturn::error("This is not a local notice.", 403);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $res = Activitypub_notice::notice_to_array($notice);
 | 
			
		||||
 | 
			
		||||
        ActivityPubReturn::answer($res);
 | 
			
		||||
    }
 | 
			
		||||
// Validate Object
 | 
			
		||||
if (!is_string ($data->object)) {
 | 
			
		||||
        ActivityPubReturn::error ("Invalid Object object, URL expected.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get valid Object profile
 | 
			
		||||
try {
 | 
			
		||||
        $object_profile = new Activitypub_explorer;
 | 
			
		||||
        $object_profile = $object_profile->lookup ($data->object)[0];
 | 
			
		||||
} catch(Exception $e) {
 | 
			
		||||
        ActivityPubReturn::error ("Invalid Object Actor URL.", 404);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
try {
 | 
			
		||||
        if (!Subscription::exists ($actor_profile, $object_profile)) {
 | 
			
		||||
                Subscription::start ($actor_profile, $object_profile);
 | 
			
		||||
                $res = array ("@context" => "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                          "type"   => "Follow",
 | 
			
		||||
                          "actor"  => $data->actor,
 | 
			
		||||
                          "object" => $data->object);
 | 
			
		||||
                ActivityPubReturn::answer ($res);
 | 
			
		||||
        } else {
 | 
			
		||||
                ActivityPubReturn::error ("Already following.", 409);
 | 
			
		||||
        }
 | 
			
		||||
} catch (Exception $e) {
 | 
			
		||||
        ActivityPubReturn::error ("Invalid Object Actor URL.", 404);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								classes/Activitypub_delete.php → actions/inbox/Like.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										43
									
								
								classes/Activitypub_delete.php → actions/inbox/Like.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -20,41 +20,22 @@
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub error representation
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_delete extends Managed_DataObject
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates an ActivityPub representation of a Delete
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param array $object
 | 
			
		||||
     * @return pretty array to be used in a response
 | 
			
		||||
     */
 | 
			
		||||
    public static function delete_to_array($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        $res = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
            'id'     => $object.'/delete',
 | 
			
		||||
            'type'   => 'Delete',
 | 
			
		||||
            'actor'  => $actor,
 | 
			
		||||
            'object' => $object
 | 
			
		||||
        ];
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
try {
 | 
			
		||||
        Fave::addNew ($actor_profile, Notice::getByUri ($data->object));
 | 
			
		||||
        $res = array ("@context" => "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                  "type"   => "Like",
 | 
			
		||||
                  "actor"  => $data->actor,
 | 
			
		||||
                  "object" => $data->object);
 | 
			
		||||
        ActivityPubReturn::answer ($res);
 | 
			
		||||
} catch (Exception $e) {
 | 
			
		||||
        ActivityPubReturn::error ($e->getMessage (), 403);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										78
									
								
								actions/inbox/Undo.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								actions/inbox/Undo.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate data
 | 
			
		||||
if (!isset ($data->type)) {
 | 
			
		||||
        ActivityPubReturn::error ("Type was not specified.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
switch ($data->object->type) {
 | 
			
		||||
case "Like":
 | 
			
		||||
        try {
 | 
			
		||||
                // Validate data
 | 
			
		||||
                if (!isset ($data->object->object)) {
 | 
			
		||||
                        ActivityPubReturn::error ("Object Notice URL was not specified.");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Fave::removeEntry ($actor_profile, Notice::getByUri ($data->object->object));
 | 
			
		||||
                ActivityPubReturn::answer ("Notice disfavorited successfully.");
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
                ActivityPubReturn::error ($e->getMessage (), 403);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
case "Follow":
 | 
			
		||||
        // Validate data
 | 
			
		||||
        if (!isset ($data->object->object)) {
 | 
			
		||||
                ActivityPubReturn::error ("Object Actor URL was not specified.");
 | 
			
		||||
        }
 | 
			
		||||
        // Get valid Object profile
 | 
			
		||||
        try {
 | 
			
		||||
                $object_profile = new Activitypub_explorer;
 | 
			
		||||
                $object_profile = $object_profile->lookup ($data->object->object)[0];
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
                ActivityPubReturn::error ("Invalid Object Actor URL.", 404);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
                if (Subscription::exists ($actor_profile, $object_profile)) {
 | 
			
		||||
                        Subscription::cancel ($actor_profile, $object_profile);
 | 
			
		||||
                        ActivityPubReturn::answer ("You are no longer following this person.");
 | 
			
		||||
                } else {
 | 
			
		||||
                        ActivityPubReturn::error ("You are not following this person already.", 409);
 | 
			
		||||
                }
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
                ActivityPubReturn::error ("Invalid Object Actor URL.", 404);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
default:
 | 
			
		||||
        ActivityPubReturn::error ("Invalid object type.");
 | 
			
		||||
        break;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,88 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub error representation
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_accept extends Managed_DataObject
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates an ActivityPub representation of a Accept
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param array $object
 | 
			
		||||
     * @return pretty array to be used in a response
 | 
			
		||||
     */
 | 
			
		||||
    public static function accept_to_array($object)
 | 
			
		||||
    {
 | 
			
		||||
        $res = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
            'id'     => common_root_url().'accept_follow_from_'.urlencode($object['actor']).'_to_'.urlencode($object['object']),
 | 
			
		||||
            'type'   => 'Accept',
 | 
			
		||||
            'actor'  => $object['object'],
 | 
			
		||||
            'object' => $object
 | 
			
		||||
        ];
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Verifies if a given object is acceptable for an Accept Activity.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Array $object
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public static function validate_object($object)
 | 
			
		||||
    {
 | 
			
		||||
        if (!is_array($object)) {
 | 
			
		||||
            throw new Exception('Invalid Object Format for Accept Activity.');
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($object['type'])) {
 | 
			
		||||
            throw new Exception('Object type was not specified for Accept Activity.');
 | 
			
		||||
        }
 | 
			
		||||
        switch ($object['type']) {
 | 
			
		||||
            case 'Follow':
 | 
			
		||||
                // Validate data
 | 
			
		||||
                if (!filter_var($object['object'], FILTER_VALIDATE_URL)) {
 | 
			
		||||
                    throw new Exception("Object is not a valid Object URI for Activity.");
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new Exception('This is not a supported Object Type for Accept Activity.');
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								classes/Activitypub_attachment.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										27
									
								
								classes/Activitypub_attachment.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -20,12 +20,13 @@
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -42,23 +43,29 @@ class Activitypub_attachment extends Managed_DataObject
 | 
			
		||||
        /**
 | 
			
		||||
         * Generates a pretty array from an Attachment object
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @param Attachment $attachment
 | 
			
		||||
         * @return pretty array to be used in a response
 | 
			
		||||
         */
 | 
			
		||||
    public static function attachment_to_array($attachment)
 | 
			
		||||
        public static function attachment_to_array ($attachment)
 | 
			
		||||
        {
 | 
			
		||||
                $res = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
            'type'      => 'Document',
 | 
			
		||||
            'mediaType' => $attachment->mimetype,
 | 
			
		||||
            'url'       => $attachment->getUrl(),
 | 
			
		||||
                        '@context'  => [
 | 
			
		||||
                                "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                                [
 | 
			
		||||
                                        "@language" => "en"
 | 
			
		||||
                                ]
 | 
			
		||||
                        ],
 | 
			
		||||
                        'id'       => $attachment->getID (),
 | 
			
		||||
                        'mimetype' => $attachment->mimetype,
 | 
			
		||||
                        'url'      => $attachment->getUrl (),
 | 
			
		||||
                        'size'     => intval($attachment->size), // $attachment->getSize ()
 | 
			
		||||
            'name'      => $attachment->getTitle(),
 | 
			
		||||
                        'title'    => $attachment->getTitle (),
 | 
			
		||||
                        'meta'     => null
 | 
			
		||||
                ];
 | 
			
		||||
 | 
			
		||||
                // Image
 | 
			
		||||
        if (substr($res["mediaType"], 0, 5) == "image") {
 | 
			
		||||
                if (substr ($res["mimetype"], 0, 5) == "image")
 | 
			
		||||
                {
 | 
			
		||||
                        $res["meta"]= [
 | 
			
		||||
                                'width'  => $attachment->width,
 | 
			
		||||
                                'height' => $attachment->height
 | 
			
		||||
 
 | 
			
		||||
@@ -1,88 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub error representation
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_create extends Managed_DataObject
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates an ActivityPub representation of a Create
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param string $actor
 | 
			
		||||
     * @param array $object
 | 
			
		||||
     * @return pretty array to be used in a response
 | 
			
		||||
     */
 | 
			
		||||
    public static function create_to_array($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        $res = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
            'id'     => $object['id'].'/create',
 | 
			
		||||
            'type'   => 'Create',
 | 
			
		||||
            'to'     => $object['to'],
 | 
			
		||||
            'cc'     => $object['cc'],
 | 
			
		||||
            'actor'  => $actor,
 | 
			
		||||
            'object' => $object
 | 
			
		||||
        ];
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Verifies if a given object is acceptable for a Create Activity.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Array $object
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public static function validate_object($object)
 | 
			
		||||
    {
 | 
			
		||||
        if (!is_array($object)) {
 | 
			
		||||
            throw new Exception('Invalid Object Format for Create Activity.');
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($object['type'])) {
 | 
			
		||||
            throw new Exception('Object type was not specified for Create Activity.');
 | 
			
		||||
        }
 | 
			
		||||
        switch ($object['type']) {
 | 
			
		||||
            case 'Note':
 | 
			
		||||
                // Validate data
 | 
			
		||||
                Activitypub_notice::validate_note($object);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new Exception('This is not a supported Object Type for Create Activity.');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								classes/Activitypub_error.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										8
									
								
								classes/Activitypub_error.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -20,12 +20,13 @@
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -42,11 +43,10 @@ class Activitypub_error extends Managed_DataObject
 | 
			
		||||
        /**
 | 
			
		||||
         * Generates a pretty error from a string
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @param string $m
 | 
			
		||||
         * @return pretty array to be used in a response
 | 
			
		||||
         */
 | 
			
		||||
    public static function error_message_to_array($m)
 | 
			
		||||
        public static function error_message_to_array ($m)
 | 
			
		||||
        {
 | 
			
		||||
                $res = [
 | 
			
		||||
                        'error'=> $m
 | 
			
		||||
 
 | 
			
		||||
@@ -1,91 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub error representation
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_follow extends Managed_DataObject
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates an ActivityPub representation of a subscription
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param string $actor
 | 
			
		||||
     * @param string $object
 | 
			
		||||
     * @return pretty array to be used in a response
 | 
			
		||||
     */
 | 
			
		||||
    public static function follow_to_array($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        $res = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
            'id'     => common_root_url().'follow_from_'.urlencode($actor).'_to_'.urlencode($object),
 | 
			
		||||
            'type'   => 'Follow',
 | 
			
		||||
            'actor'  => $actor,
 | 
			
		||||
            'object' => $object
 | 
			
		||||
       ];
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a Follow Activity received by our inbox.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Profile $actor_profile Remote Actor
 | 
			
		||||
     * @param string $object Local Actor
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public static function follow($actor_profile, $object)
 | 
			
		||||
    {
 | 
			
		||||
        // Get Actor's Aprofile
 | 
			
		||||
        $actor_aprofile = Activitypub_profile::from_profile($actor_profile);
 | 
			
		||||
 | 
			
		||||
        // Get Object profile
 | 
			
		||||
        $object_profile = new Activitypub_explorer;
 | 
			
		||||
        $object_profile = $object_profile->lookup($object)[0];
 | 
			
		||||
 | 
			
		||||
        if (!Subscription::exists($actor_profile, $object_profile)) {
 | 
			
		||||
            Subscription::start($actor_profile, $object_profile);
 | 
			
		||||
            common_debug('ActivityPubPlugin: Accepted Follow request from '.ActivityPubPlugin::actor_uri($actor_profile).' to '.$object);
 | 
			
		||||
        } else {
 | 
			
		||||
            common_debug('ActivityPubPlugin: Received a repeated Follow request from '.ActivityPubPlugin::actor_uri($actor_profile).' to '.$object);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Notify remote instance that we have accepted their request
 | 
			
		||||
        common_debug('ActivityPubPlugin: Notifying remote instance that we have accepted their Follow request request from '.ActivityPubPlugin::actor_uri($actor_profile).' to '.$object);
 | 
			
		||||
        $postman = new Activitypub_postman($actor_profile, [$actor_aprofile]);
 | 
			
		||||
        $postman->accept_follow();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,61 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub error representation
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_like extends Managed_DataObject
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates an ActivityPub representation of a Like
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param string $actor  Actor URI
 | 
			
		||||
     * @param string $object Notice URI
 | 
			
		||||
     * @return pretty array to be used in a response
 | 
			
		||||
     */
 | 
			
		||||
    public static function like_to_array($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        $res = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
            'id'     => common_root_url().'like_from_'.urlencode($actor).'_to_'.urlencode($object),
 | 
			
		||||
            "type"   => "Like",
 | 
			
		||||
            "actor"  => $actor,
 | 
			
		||||
            "object" => $object
 | 
			
		||||
        ];
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,60 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub Mention Tag representation
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_mention_tag extends Managed_DataObject
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates an ActivityPub representation of a Mention Tag
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param string $href Actor Uri
 | 
			
		||||
     * @param array $name Mention name
 | 
			
		||||
     * @return pretty array to be used in a response
 | 
			
		||||
     */
 | 
			
		||||
    public static function mention_tag_to_array_from_values($href, $name)
 | 
			
		||||
    {
 | 
			
		||||
        $res = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
            "type" => "Mention",
 | 
			
		||||
            "href" => $href,
 | 
			
		||||
            "name" => $name
 | 
			
		||||
         ];
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										256
									
								
								classes/Activitypub_notice.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										256
									
								
								classes/Activitypub_notice.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -19,13 +19,14 @@
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -33,6 +34,7 @@ if (!defined('GNUSOCIAL')) {
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
@@ -42,250 +44,46 @@ class Activitypub_notice extends Managed_DataObject
 | 
			
		||||
        /**
 | 
			
		||||
         * Generates a pretty notice from a Notice object
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @param Notice $notice
 | 
			
		||||
         * @return pretty array to be used in a response
 | 
			
		||||
         */
 | 
			
		||||
    public static function notice_to_array($notice)
 | 
			
		||||
        public static function notice_to_array ($notice)
 | 
			
		||||
        {
 | 
			
		||||
        $profile = $notice->getProfile();
 | 
			
		||||
        $attachments = [];
 | 
			
		||||
        foreach ($notice->attachments() as $attachment) {
 | 
			
		||||
            $attachments[] = Activitypub_attachment::attachment_to_array($attachment);
 | 
			
		||||
                $attachments = array ();
 | 
			
		||||
                foreach($notice->attachments () as $attachment) {
 | 
			
		||||
                        $attachments[] = Activitypub_attachment::attachment_to_array ($attachment);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        $tags = [];
 | 
			
		||||
        foreach ($notice->getTags() as $tag) {
 | 
			
		||||
                $tags = array ();
 | 
			
		||||
                foreach($notice->getTags()as $tag) {
 | 
			
		||||
                        if ($tag != "") {       // Hacky workaround to avoid stupid outputs
 | 
			
		||||
                $tags[] = Activitypub_tag::tag_to_array($tag);
 | 
			
		||||
                                $tags[] = Activitypub_tag::tag_to_array ($tag);
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        $cc = [common_local_url('apActorFollowers', ['id' => $profile->getID()])];
 | 
			
		||||
        foreach ($notice->getAttentionProfiles() as $to_profile) {
 | 
			
		||||
            $cc[]  = $href = $to_profile->getUri();
 | 
			
		||||
            $tags[] = Activitypub_mention_tag::mention_tag_to_array_from_values($href, $to_profile->getNickname().'@'.parse_url($href, PHP_URL_HOST));
 | 
			
		||||
                $to = array ();
 | 
			
		||||
                foreach ($notice->getAttentionProfileIDs () as $to_id) {
 | 
			
		||||
                        $to[] = Profile::getById ($to_id)->getUri ();
 | 
			
		||||
                }
 | 
			
		||||
                if (!is_null($to)) {
 | 
			
		||||
                        $to = array ("https://www.w3.org/ns/activitystreams#Public");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        // In a world without walls and fences, we should make everything Public!
 | 
			
		||||
        $to[]= 'https://www.w3.org/ns/activitystreams#Public';
 | 
			
		||||
 | 
			
		||||
                $item = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
            'id'           => common_local_url('apNotice', ['id' => $notice->getID()]),
 | 
			
		||||
            'type'         => 'Note',
 | 
			
		||||
            'published'    => str_replace(' ', 'T', $notice->getCreated()).'Z',
 | 
			
		||||
            'url'          => $notice->getUrl(),
 | 
			
		||||
            'attributedTo' => ActivityPubPlugin::actor_uri($profile),
 | 
			
		||||
            'to'           => ['https://www.w3.org/ns/activitystreams#Public'],
 | 
			
		||||
            'cc'           => $cc,
 | 
			
		||||
            'atomUri'      => $notice->getUrl(),
 | 
			
		||||
            'conversation' => $notice->getConversationUrl(),
 | 
			
		||||
            'content'      => $notice->getRendered(),
 | 
			
		||||
            'isLocal'      => $notice->isLocal(),
 | 
			
		||||
                        'id'           => $notice->getUrl (),
 | 
			
		||||
                        'type'         => 'Notice',
 | 
			
		||||
                        'actor'        => $notice->getProfile ()->getUrl (),
 | 
			
		||||
                        'published'    => $notice->getCreated (),
 | 
			
		||||
                        'to'           => $to,
 | 
			
		||||
                        'content'      => $notice->getContent (),
 | 
			
		||||
                        'url'          => $notice->getUrl (),
 | 
			
		||||
                        'reply_to'     => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUrl (),
 | 
			
		||||
                        'is_local'     => $notice->isLocal (),
 | 
			
		||||
                        'conversation' => intval ($notice->conversation),
 | 
			
		||||
                        'attachment'   => $attachments,
 | 
			
		||||
                        'tag'          => $tags
 | 
			
		||||
                ];
 | 
			
		||||
 | 
			
		||||
        // Is this a reply?
 | 
			
		||||
        if (!empty($notice->reply_to)) {
 | 
			
		||||
            $item['inReplyTo'] = common_local_url('apNotice', ['id' => $notice->getID()]);
 | 
			
		||||
            $item['inReplyToAtomUri'] = Notice::getById($notice->reply_to)->getUrl();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Do we have a location for this notice?
 | 
			
		||||
        try {
 | 
			
		||||
            $location = Notice_location::locFromStored($notice);
 | 
			
		||||
            $item['latitude']  = $location->lat;
 | 
			
		||||
            $item['longitude'] = $location->lon;
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            // Apparently no.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                return $item;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a Notice via ActivityPub Note Object.
 | 
			
		||||
     * Returns created Notice.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Array $object
 | 
			
		||||
     * @param Profile|null $actor_profile
 | 
			
		||||
     * @return Notice
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public static function create_notice($object, $actor_profile = null)
 | 
			
		||||
    {
 | 
			
		||||
        $id      = $object['id'];         // int32
 | 
			
		||||
        $url     = $object['url'];        // string
 | 
			
		||||
        $content = $object['content'];    // string
 | 
			
		||||
 | 
			
		||||
        // possible keys: ['inReplyTo', 'latitude', 'longitude', 'attachment']
 | 
			
		||||
        $settings = [];
 | 
			
		||||
        if (isset($object['inReplyTo'])) {
 | 
			
		||||
            $settings['inReplyTo'] = $object['inReplyTo'];
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($object['latitude'])) {
 | 
			
		||||
            $settings['latitude']  = $object['latitude'];
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($object['longitude'])) {
 | 
			
		||||
            $settings['longitude'] = $object['longitude'];
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($object['attachment'])) {
 | 
			
		||||
            $settings['attachment'] = $object['attachment'];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Ensure Actor Profile
 | 
			
		||||
        if (is_null($actor_profile)) {
 | 
			
		||||
            $actor_profile = ActivityPub_explorer::get_profile_from_url($object['actor']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $act = new Activity();
 | 
			
		||||
        $act->verb = ActivityVerb::POST;
 | 
			
		||||
        $act->time = time();
 | 
			
		||||
        $act->actor = $actor_profile->asActivityObject();
 | 
			
		||||
        $act->context = new ActivityContext();
 | 
			
		||||
        $options = ['source' => 'ActivityPub', 'uri' => $id, 'url' => $url];
 | 
			
		||||
 | 
			
		||||
        // Do we have an attachment?
 | 
			
		||||
        if (isset($settings['attachment'][0])) {
 | 
			
		||||
            $attach = $settings['attachment'][0];
 | 
			
		||||
            $attach_url = $settings['attachment'][0]['url'];
 | 
			
		||||
            // Is it an image?
 | 
			
		||||
            if (ActivityPubPlugin::$store_images_from_remote_notes_attachments && substr($attach["mediaType"], 0, 5) == "image") {
 | 
			
		||||
                $temp_filename = tempnam(sys_get_temp_dir(), 'apCreateNoteAttach_');
 | 
			
		||||
                try {
 | 
			
		||||
                    $imgData = HTTPClient::quickGet($attach_url);
 | 
			
		||||
                    // Make sure it's at least an image file. ImageFile can do the rest.
 | 
			
		||||
                    if (false === getimagesizefromstring($imgData)) {
 | 
			
		||||
                        common_debug('ActivityPub Create Notice: Failed because the downloaded image: '.$attach_url. 'is not valid.');
 | 
			
		||||
                        throw new UnsupportedMediaException('Downloaded image was not an image.');
 | 
			
		||||
                    }
 | 
			
		||||
                    file_put_contents($temp_filename, $imgData);
 | 
			
		||||
                    common_debug('ActivityPub Create Notice: Stored dowloaded image in: '.$temp_filename);
 | 
			
		||||
 | 
			
		||||
                    $id = $actor_profile->getID();
 | 
			
		||||
 | 
			
		||||
                    $imagefile = new ImageFile(null, $temp_filename);
 | 
			
		||||
                    $filename = hash(File::FILEHASH_ALG, $imgData).image_type_to_extension($imagefile->type);
 | 
			
		||||
 | 
			
		||||
                    unset($imgData);    // No need to carry this in memory.
 | 
			
		||||
                    rename($temp_filename, File::path($filename));
 | 
			
		||||
                    common_debug('ActivityPub Create Notice: Moved image from: '.$temp_filename.' to '.$filename);
 | 
			
		||||
                    $mediaFile = new MediaFile($filename, $attach['mediaType']);
 | 
			
		||||
                    $act->enclosures[] = $mediaFile->getEnclosure();
 | 
			
		||||
                } catch (Exception $e) {
 | 
			
		||||
                    common_debug('ActivityPub Create Notice: Something went wrong while processing the image from: '.$attach_url.' details: '.$e->getMessage());
 | 
			
		||||
                    unlink($temp_filename);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $content .= ($content==='' ? '' : ' ') . '<br><a href="'.$attach_url.'">Remote Attachment Source</a>';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Is this a reply?
 | 
			
		||||
        if (isset($settings['inReplyTo'])) {
 | 
			
		||||
            try {
 | 
			
		||||
                $inReplyTo = ActivityPubPlugin::grab_notice_from_url($settings['inReplyTo']);
 | 
			
		||||
                $act->context->replyToID  = $inReplyTo->getUri();
 | 
			
		||||
                $act->context->replyToUrl = $inReplyTo->getUrl();
 | 
			
		||||
            } catch (Exception $e) {
 | 
			
		||||
                // It failed to grab, maybe we got this note from another source
 | 
			
		||||
                // (e.g.: OStatus) that handles this differently or we really
 | 
			
		||||
                // failed to get it...
 | 
			
		||||
                // Welp, nothing that we can do about, let's
 | 
			
		||||
                // just fake we don't have such notice.
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            $inReplyTo = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Mentions
 | 
			
		||||
        $mentions = [];
 | 
			
		||||
        if (isset($object['tag']) && is_array($object['tag'])) {
 | 
			
		||||
            foreach ($object['tag'] as $tag) {
 | 
			
		||||
                if ($tag['type'] == 'Mention') {
 | 
			
		||||
                    $mentions[] = $tag['href'];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $mentions_profiles = [];
 | 
			
		||||
        $discovery = new Activitypub_explorer;
 | 
			
		||||
        foreach ($mentions as $mention) {
 | 
			
		||||
            try {
 | 
			
		||||
                $mentions_profiles[] = $discovery->lookup($mention)[0];
 | 
			
		||||
            } catch (Exception $e) {
 | 
			
		||||
                // Invalid actor found, just let it go. // TODO: Fallback to OStatus
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        unset($discovery);
 | 
			
		||||
 | 
			
		||||
        foreach ($mentions_profiles as $mp) {
 | 
			
		||||
            $act->context->attention[ActivityPubPlugin::actor_uri($mp)] = 'http://activitystrea.ms/schema/1.0/person';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add location if that is set
 | 
			
		||||
        if (isset($settings['latitude'], $settings['longitude'])) {
 | 
			
		||||
            $act->context->location = Location::fromLatLon($settings['latitude'], $settings['longitude']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Reject notice if it is too long (without the HTML)
 | 
			
		||||
        if (Notice::contentTooLong($content)) {
 | 
			
		||||
            //throw new Exception('That\'s too long. Maximum notice size is %d character.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $actobj = new ActivityObject();
 | 
			
		||||
        $actobj->type = ActivityObject::NOTE;
 | 
			
		||||
        $actobj->content = strip_tags($content, '<p><b><i><u><a><ul><ol><li>');
 | 
			
		||||
 | 
			
		||||
        // Finally add the activity object to our activity
 | 
			
		||||
        $act->objects[] = $actobj;
 | 
			
		||||
 | 
			
		||||
        $note = Notice::saveActivity($act, $actor_profile, $options);
 | 
			
		||||
        if (ActivityPubPlugin::$store_images_from_remote_notes_attachments && isset($mediaFile)) {
 | 
			
		||||
            $mediaFile->attachToNotice($note);
 | 
			
		||||
        }
 | 
			
		||||
        return $note;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validates a note.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param  Array $object
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public static function validate_note($object)
 | 
			
		||||
    {
 | 
			
		||||
        if (!isset($object['attributedTo'])) {
 | 
			
		||||
            common_debug('ActivityPub Notice Validator: Rejected because attributedTo was not specified.');
 | 
			
		||||
            throw new Exception('No attributedTo specified.');
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($object['id'])) {
 | 
			
		||||
            common_debug('ActivityPub Notice Validator: Rejected because Object ID was not specified.');
 | 
			
		||||
            throw new Exception('Object ID not specified.');
 | 
			
		||||
        } elseif (!filter_var($object['id'], FILTER_VALIDATE_URL)) {
 | 
			
		||||
            common_debug('ActivityPub Notice Validator: Rejected because Object ID is invalid.');
 | 
			
		||||
            throw new Exception('Invalid Object ID.');
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($object['type']) || $object['type'] !== 'Note') {
 | 
			
		||||
            common_debug('ActivityPub Notice Validator: Rejected because of Type.');
 | 
			
		||||
            throw new Exception('Invalid Object type.');
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($object['content'])) {
 | 
			
		||||
            common_debug('ActivityPub Notice Validator: Rejected because Content was not specified.');
 | 
			
		||||
            throw new Exception('Object content was not specified.');
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($object['url'])) {
 | 
			
		||||
            throw new Exception('Object URL was not specified.');
 | 
			
		||||
        } elseif (!filter_var($object['url'], FILTER_VALIDATE_URL)) {
 | 
			
		||||
            common_debug('ActivityPub Notice Validator: Rejected because Object URL is invalid.');
 | 
			
		||||
            throw new Exception('Invalid Object URL.');
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($object['cc'])) {
 | 
			
		||||
            common_debug('ActivityPub Notice Validator: Rejected because Object CC was not specified.');
 | 
			
		||||
            throw new Exception('Object CC was not specified.');
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,117 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub's Pending follow requests
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_pending_follow_requests extends Managed_DataObject
 | 
			
		||||
{
 | 
			
		||||
    public $__table = 'Activitypub_pending_follow_requests';
 | 
			
		||||
    public $local_profile_id;
 | 
			
		||||
    public $remote_profile_id;
 | 
			
		||||
    private $_reldb = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return table definition for Schema setup and DB_DataObject usage.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @return array array of column definitions
 | 
			
		||||
     */
 | 
			
		||||
    public static function schemaDef()
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            'fields' => [
 | 
			
		||||
                'local_profile_id'  => ['type' => 'integer', 'not null' => true],
 | 
			
		||||
                'remote_profile_id' => ['type' => 'integer', 'not null' => true],
 | 
			
		||||
                'relation_id'       => ['type' => 'serial',  'not null' => true],
 | 
			
		||||
            ],
 | 
			
		||||
            'primary key' => ['relation_id'],
 | 
			
		||||
            'unique keys' => [
 | 
			
		||||
                'Activitypub_pending_follow_requests_relation_id_key' => ['relation_id'],
 | 
			
		||||
            ],
 | 
			
		||||
            'foreign keys' => [
 | 
			
		||||
                'Activitypub_pending_follow_requests_local_profile_id_fkey'  => ['profile', ['local_profile_id' => 'id']],
 | 
			
		||||
                'Activitypub_pending_follow_requests_remote_profile_id_fkey' => ['profile', ['remote_profile_id' => 'id']],
 | 
			
		||||
            ],
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function __construct($actor, $remote_actor)
 | 
			
		||||
    {
 | 
			
		||||
        $this->local_profile_id  = $actor;
 | 
			
		||||
        $this->remote_profile_id = $remote_actor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add Follow request to table.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param int32 $actor actor id
 | 
			
		||||
     * @param int32 $remote_actor remote actor id
 | 
			
		||||
     * @return boolean true if added, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public function add()
 | 
			
		||||
    {
 | 
			
		||||
        return !$this->exists() && $this->insert();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if a Follow request is pending.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @return boolean true if is pending, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public function exists()
 | 
			
		||||
    {
 | 
			
		||||
        $this->_reldb = clone ($this);
 | 
			
		||||
        if ($this->_reldb->find() > 0) {
 | 
			
		||||
            $this->_reldb->fetch();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove a request from the pending table.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @return boolean true if removed, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public function remove()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->exists() && $this->_reldb->delete();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										417
									
								
								classes/Activitypub_profile.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										417
									
								
								classes/Activitypub_profile.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -20,12 +20,13 @@
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -34,158 +35,132 @@ if (!defined('GNUSOCIAL')) {
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_profile extends Managed_DataObject
 | 
			
		||||
class Activitypub_profile extends Profile
 | 
			
		||||
{
 | 
			
		||||
        public $__table = 'Activitypub_profile';
 | 
			
		||||
    public $uri;                             // text()   not_null
 | 
			
		||||
    public $profile_id;                      // int(4)  primary_key not_null
 | 
			
		||||
    public $inboxuri;                        // text()   not_null
 | 
			
		||||
    public $sharedInboxuri;                  // text()
 | 
			
		||||
    public $nickname;                        // varchar(64)  multiple_key not_null
 | 
			
		||||
    public $fullname;                        // text()
 | 
			
		||||
    public $profileurl;                      // text()
 | 
			
		||||
    public $homepage;                        // text()
 | 
			
		||||
    public $bio;                             // text()  multiple_key
 | 
			
		||||
    public $location;                        // text()
 | 
			
		||||
    public $created;                         // datetime()   not_null
 | 
			
		||||
    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
 | 
			
		||||
 | 
			
		||||
        protected $_profile = null;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Return table definition for Schema setup and DB_DataObject usage.
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @return array array of column definitions
 | 
			
		||||
         */
 | 
			
		||||
    public static function schemaDef()
 | 
			
		||||
        static function schemaDef ()
 | 
			
		||||
        {
 | 
			
		||||
        return [
 | 
			
		||||
            'fields' => [
 | 
			
		||||
                'uri' => ['type' => 'text', 'not null' => true],
 | 
			
		||||
                'profile_id' => ['type' => 'integer'],
 | 
			
		||||
                'inboxuri' => ['type' => 'text', 'not null' => true],
 | 
			
		||||
                'sharedInboxuri' => ['type' => 'text'],
 | 
			
		||||
                'created' => ['type' => 'datetime', 'not null' => true],
 | 
			
		||||
                'modified' => ['type' => 'datetime', 'not null' => true],
 | 
			
		||||
            ],
 | 
			
		||||
            'primary key' => ['profile_id'],
 | 
			
		||||
            'unique keys' => [
 | 
			
		||||
                'Activitypub_profile_profile_id_key' => ['profile_id'],
 | 
			
		||||
            ],
 | 
			
		||||
            'foreign keys' => [
 | 
			
		||||
                'Activitypub_profile_profile_id_fkey' => ['profile', ['profile_id' => 'id']],
 | 
			
		||||
            ],
 | 
			
		||||
        ];
 | 
			
		||||
            return array (
 | 
			
		||||
                'fields' => array (
 | 
			
		||||
                    'uri' => array ('type' => 'varchar', 'length' => 191, 'not null' => true),
 | 
			
		||||
                    'profile_id' => array ('type' => 'integer'),
 | 
			
		||||
                    'inboxuri' => array ('type' => 'varchar', 'length' => 191),
 | 
			
		||||
                    'sharedInboxuri' => array ('type' => 'varchar', 'length' => 191),
 | 
			
		||||
                    'created' => array ('type' => 'datetime', 'not null' => true),
 | 
			
		||||
                    'modified' => array ('type' => 'datetime', 'not null' => true),
 | 
			
		||||
                ),
 | 
			
		||||
                'primary key' => array ('uri'),
 | 
			
		||||
                'unique keys' => array (
 | 
			
		||||
                    'Activitypub_profile_profile_id_key' => array ('profile_id'),
 | 
			
		||||
                    'Activitypub_profile_inboxuri_key' => array ('inboxuri'),
 | 
			
		||||
                ),
 | 
			
		||||
                'foreign keys' => array (
 | 
			
		||||
                    'Activitypub_profile_profile_id_fkey' => array ('profile', array ('profile_id' => 'id')),
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Generates a pretty profile from a Profile object
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @param Profile $profile
 | 
			
		||||
         * @return pretty array to be used in a response
 | 
			
		||||
         */
 | 
			
		||||
    public static function profile_to_array($profile)
 | 
			
		||||
        public static function profile_to_array ($profile)
 | 
			
		||||
        {
 | 
			
		||||
        $uri = ActivityPubPlugin::actor_uri($profile);
 | 
			
		||||
        $id = $profile->getID();
 | 
			
		||||
        $rsa = new Activitypub_rsa();
 | 
			
		||||
        $public_key = $rsa->ensure_public_key($profile);
 | 
			
		||||
        unset($rsa);
 | 
			
		||||
                $url = $profile->getURL ();
 | 
			
		||||
                $res = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
            'id'                => $uri,
 | 
			
		||||
            'type'              => 'Person',
 | 
			
		||||
            'following'         => common_local_url('apActorFollowing', ['id' => $id]),
 | 
			
		||||
            'followers'         => common_local_url('apActorFollowers', ['id' => $id]),
 | 
			
		||||
            'liked'             => common_local_url('apActorLiked', ['id' => $id]),
 | 
			
		||||
            'inbox'             => common_local_url('apInbox', ['id' => $id]),
 | 
			
		||||
            'outbox'            => common_local_url('apActorOutbox', ['id' => $id]),
 | 
			
		||||
            'preferredUsername' => $profile->getNickname(),
 | 
			
		||||
            'name'              => $profile->getBestName(),
 | 
			
		||||
            'summary'           => ($desc = $profile->getDescription()) == null ? "" : $desc,
 | 
			
		||||
            'url'               => $profile->getUrl(),
 | 
			
		||||
            'manuallyApprovesFollowers' => false,
 | 
			
		||||
            'publicKey' => [
 | 
			
		||||
                'id'    => $uri."#public-key",
 | 
			
		||||
                'owner' => $uri,
 | 
			
		||||
                'publicKeyPem' => $public_key
 | 
			
		||||
                        '@context'        => [
 | 
			
		||||
                                "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                                [
 | 
			
		||||
                                        "@language"   => "en"
 | 
			
		||||
                                ]
 | 
			
		||||
                        ],
 | 
			
		||||
            'tag' => [],
 | 
			
		||||
            'attachment' => [],
 | 
			
		||||
            'icon' => [
 | 
			
		||||
                        'id'              => $profile->getID (),
 | 
			
		||||
                        'type'            => 'Person',
 | 
			
		||||
                        'nickname'        => $profile->getNickname (),
 | 
			
		||||
                        'is_local'        => $profile->isLocal (),
 | 
			
		||||
                        'inbox'           => "{$url}/inbox.json",
 | 
			
		||||
                        'sharedInbox'     => common_root_url ()."inbox.json",
 | 
			
		||||
                        'outbox'          => "{$url}/outbox.json",
 | 
			
		||||
                        'display_name'    => $profile->getFullname (),
 | 
			
		||||
                        'followers'       => "{$url}/followers.json",
 | 
			
		||||
                        'followers_count' => $profile->subscriberCount (),
 | 
			
		||||
                        'following'       => "{$url}/following.json",
 | 
			
		||||
                        'following_count' => $profile->subscriptionCount (),
 | 
			
		||||
                        'liked'           => "{$url}/liked.json",
 | 
			
		||||
                        'liked_count'     => Fave::countByProfile ($profile),
 | 
			
		||||
                        'summary'         => ($desc = $profile->getDescription ()) == null ? "" : $desc,
 | 
			
		||||
                        'url'             => $profile->getURL (),
 | 
			
		||||
                        'avatar'          => [
 | 
			
		||||
                                'type'   => 'Image',
 | 
			
		||||
                'mediaType' => 'image/png',
 | 
			
		||||
                'height'    => AVATAR_PROFILE_SIZE,
 | 
			
		||||
                'width'     => AVATAR_PROFILE_SIZE,
 | 
			
		||||
                'url'       => $profile->avatarUrl(AVATAR_PROFILE_SIZE)
 | 
			
		||||
                                'width'  => 96,
 | 
			
		||||
                                'height' => 96,
 | 
			
		||||
                                'url'    => $profile->avatarUrl (AVATAR_PROFILE_SIZE)
 | 
			
		||||
                        ]
 | 
			
		||||
                ];
 | 
			
		||||
 | 
			
		||||
        if ($profile->isLocal()) {
 | 
			
		||||
            $res['endpoints']['sharedInbox'] = common_local_url('apInbox');
 | 
			
		||||
        } else {
 | 
			
		||||
            $aprofile = new Activitypub_profile();
 | 
			
		||||
            $aprofile = $aprofile->from_profile($profile);
 | 
			
		||||
            $res['endpoints']['sharedInbox'] = $aprofile->sharedInboxuri;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                return $res;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
     * Insert the current object variables into the database
 | 
			
		||||
         * Insert the current objects variables into the database
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @access public
 | 
			
		||||
         * @throws ServerException
 | 
			
		||||
         */
 | 
			
		||||
    public function do_insert()
 | 
			
		||||
        public function doInsert ()
 | 
			
		||||
        {
 | 
			
		||||
        $profile = new Profile();
 | 
			
		||||
                $profile = new Profile ();
 | 
			
		||||
 | 
			
		||||
        $profile->created = $this->created = $this->modified = common_sql_now();
 | 
			
		||||
                $profile->created = $this->created = $this->modified = common_sql_now ();
 | 
			
		||||
 | 
			
		||||
        $fields = [
 | 
			
		||||
                $fields = array (
 | 
			
		||||
                            'uri'      => 'profileurl',
 | 
			
		||||
                            'nickname' => 'nickname',
 | 
			
		||||
                            'fullname' => 'fullname',
 | 
			
		||||
                            'bio'      => 'bio'
 | 
			
		||||
                    ];
 | 
			
		||||
                            );
 | 
			
		||||
 | 
			
		||||
                foreach ($fields as $af => $pf) {
 | 
			
		||||
                        $profile->$pf = $this->$af;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        $this->profile_id = $profile->insert();
 | 
			
		||||
                $this->profile_id = $profile->insert ();
 | 
			
		||||
                if ($this->profile_id === false) {
 | 
			
		||||
            $profile->query('ROLLBACK');
 | 
			
		||||
            throw new ServerException('Profile insertion failed.');
 | 
			
		||||
                        $profile->query ('ROLLBACK');
 | 
			
		||||
                        throw new ServerException ('Profile insertion failed.');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        $ok = $this->insert();
 | 
			
		||||
                $ok = $this->insert ();
 | 
			
		||||
 | 
			
		||||
                if ($ok === false) {
 | 
			
		||||
            $profile->query('ROLLBACK');
 | 
			
		||||
            $this->query('ROLLBACK');
 | 
			
		||||
            throw new ServerException('Cannot save ActivityPub profile.');
 | 
			
		||||
                        $profile->query ('ROLLBACK');
 | 
			
		||||
                        throw new ServerException ('Cannot save ActivityPub profile.');
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Fetch the locally stored profile for this Activitypub_profile
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @return Profile
 | 
			
		||||
         * @throws NoProfileException if it was not found
 | 
			
		||||
         */
 | 
			
		||||
    public function local_profile()
 | 
			
		||||
        public function localProfile ()
 | 
			
		||||
        {
 | 
			
		||||
        $profile = Profile::getKV('id', $this->profile_id);
 | 
			
		||||
                $profile = Profile::getKV ('id', $this->profile_id);
 | 
			
		||||
                if (!$profile instanceof Profile) {
 | 
			
		||||
            throw new NoProfileException($this->profile_id);
 | 
			
		||||
                        throw new NoProfileException ($this->profile_id);
 | 
			
		||||
                }
 | 
			
		||||
                return $profile;
 | 
			
		||||
        }
 | 
			
		||||
@@ -193,277 +168,37 @@ class Activitypub_profile extends Managed_DataObject
 | 
			
		||||
        /**
 | 
			
		||||
         * Generates an Activitypub_profile from a Profile
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @param Profile $profile
 | 
			
		||||
         * @return Activitypub_profile
 | 
			
		||||
         * @throws Exception if no Activitypub_profile exists for given Profile
 | 
			
		||||
         */
 | 
			
		||||
    public static function from_profile(Profile $profile)
 | 
			
		||||
        static function fromProfile (Profile $profile)
 | 
			
		||||
        {
 | 
			
		||||
        $profile_id = $profile->getID();
 | 
			
		||||
                $profile_id = $profile->getID ();
 | 
			
		||||
 | 
			
		||||
        $aprofile = self::getKV('profile_id', $profile_id);
 | 
			
		||||
                $aprofile = Activitypub_profile::getKV ('profile_id', $profile_id);
 | 
			
		||||
                if (!$aprofile instanceof Activitypub_profile) {
 | 
			
		||||
            // No Activitypub_profile for this profile_id,
 | 
			
		||||
            if (!$profile->isLocal()) {
 | 
			
		||||
                // create one!
 | 
			
		||||
                $aprofile = self::create_from_local_profile($profile);
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new Exception('No Activitypub_profile for Profile ID: '.$profile_id. ', this is a local user.');
 | 
			
		||||
            }
 | 
			
		||||
                        throw new Exception('No Activitypub_profile for Profile ID: '.$profile_id);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        $fields = [
 | 
			
		||||
                    'uri'      => 'profileurl',
 | 
			
		||||
                    'nickname' => 'nickname',
 | 
			
		||||
                    'fullname' => 'fullname',
 | 
			
		||||
                    'bio'      => 'bio'
 | 
			
		||||
                    ];
 | 
			
		||||
 | 
			
		||||
        foreach ($fields as $af => $pf) {
 | 
			
		||||
            $aprofile->$af = $profile->$pf;
 | 
			
		||||
                foreach ($profile as $key => $value) {
 | 
			
		||||
                        $aprofile->$key = $value;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return $aprofile;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given an existent local profile creates an ActivityPub profile.
 | 
			
		||||
     * One must be careful not to give a user profile to this function
 | 
			
		||||
     * as only remote users have ActivityPub_profiles on local instance
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Profile $profile
 | 
			
		||||
     * @return Activitypub_profile
 | 
			
		||||
     */
 | 
			
		||||
    private static function create_from_local_profile(Profile $profile)
 | 
			
		||||
    {
 | 
			
		||||
        $url = $profile->getUri();
 | 
			
		||||
        $inboxes = Activitypub_explorer::get_actor_inboxes_uri($url);
 | 
			
		||||
 | 
			
		||||
        if ($inboxes == null) {
 | 
			
		||||
            throw new Exception('This is not an ActivityPub user thus AProfile is politely refusing to proceed.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $aprofile->created = $aprofile->modified = common_sql_now();
 | 
			
		||||
 | 
			
		||||
        $aprofile                 = new Activitypub_profile;
 | 
			
		||||
        $aprofile->profile_id     = $profile->getID();
 | 
			
		||||
        $aprofile->uri            = $url;
 | 
			
		||||
        $aprofile->nickname       = $profile->getNickname();
 | 
			
		||||
        $aprofile->fullname       = $profile->getFullname();
 | 
			
		||||
        $aprofile->bio            = substr($profile->getDescription(), 0, 1000);
 | 
			
		||||
        $aprofile->inboxuri       = $inboxes["inbox"];
 | 
			
		||||
        $aprofile->sharedInboxuri = $inboxes["sharedInbox"];
 | 
			
		||||
 | 
			
		||||
        $aprofile->insert();
 | 
			
		||||
 | 
			
		||||
        return $aprofile;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns sharedInbox if possible, inbox otherwise
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @return string Inbox URL
 | 
			
		||||
         */
 | 
			
		||||
    public function get_inbox()
 | 
			
		||||
        public function getInbox ()
 | 
			
		||||
        {
 | 
			
		||||
        if (is_null($this->sharedInboxuri)) {
 | 
			
		||||
                if (is_null ($this->sharedInboxuri)) {
 | 
			
		||||
                        return $this->inboxuri;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return $this->sharedInboxuri;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Getter for uri property
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @return string URI
 | 
			
		||||
     */
 | 
			
		||||
    public function getUri()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->uri;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Getter for url property
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @return string URL
 | 
			
		||||
     */
 | 
			
		||||
    public function getUrl()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getUri();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Getter for id property
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @return int32
 | 
			
		||||
     */
 | 
			
		||||
    public function getID()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->profile_id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Ensures a valid Activitypub_profile when provided with a valid URI.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param string $url
 | 
			
		||||
     * @return Activitypub_profile
 | 
			
		||||
     * @throws Exception if it isn't possible to return an Activitypub_profile
 | 
			
		||||
     */
 | 
			
		||||
    public static function fromUri($url)
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            return self::from_profile(Activitypub_explorer::get_profile_from_url($url));
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            throw new Exception('No valid ActivityPub profile found for given URI.');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Look up, and if necessary create, an Activitypub_profile for the remote
 | 
			
		||||
     * entity with the given webfinger address.
 | 
			
		||||
     * This should never return null -- you will either get an object or
 | 
			
		||||
     * an exception will be thrown.
 | 
			
		||||
     *
 | 
			
		||||
     * @author GNU Social
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param string $addr webfinger address
 | 
			
		||||
     * @return Activitypub_profile
 | 
			
		||||
     * @throws Exception on error conditions
 | 
			
		||||
     */
 | 
			
		||||
    public static function ensure_web_finger($addr)
 | 
			
		||||
    {
 | 
			
		||||
        // Normalize $addr, i.e. add 'acct:' if missing
 | 
			
		||||
        $addr = Discovery::normalize($addr);
 | 
			
		||||
 | 
			
		||||
        // Try the cache
 | 
			
		||||
        $uri = self::cacheGet(sprintf('activitypub_profile:webfinger:%s', $addr));
 | 
			
		||||
 | 
			
		||||
        if ($uri !== false) {
 | 
			
		||||
            if (is_null($uri)) {
 | 
			
		||||
                // Negative cache entry
 | 
			
		||||
                // TRANS: Exception.
 | 
			
		||||
                throw new Exception(_m('Not a valid webfinger address (via cache).'));
 | 
			
		||||
            }
 | 
			
		||||
            try {
 | 
			
		||||
                return self::fromUri($uri);
 | 
			
		||||
            } catch (Exception $e) {
 | 
			
		||||
                common_log(LOG_ERR, sprintf(__METHOD__ . ': Webfinger address cache inconsistent with database, did not find Activitypub_profile uri==%s', $uri));
 | 
			
		||||
                self::cacheSet(sprintf('activitypub_profile:webfinger:%s', $addr), false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Now, try some discovery
 | 
			
		||||
 | 
			
		||||
        $disco = new Discovery();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            $xrd = $disco->lookup($addr);
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            // Save negative cache entry so we don't waste time looking it up again.
 | 
			
		||||
            // @todo FIXME: Distinguish temporary failures?
 | 
			
		||||
            self::cacheSet(sprintf('activitypub_profile:webfinger:%s', $addr), null);
 | 
			
		||||
            // TRANS: Exception.
 | 
			
		||||
            throw new Exception(_m('Not a valid webfinger address.'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $hints = array_merge(
 | 
			
		||||
                    array('webfinger' => $addr),
 | 
			
		||||
                DiscoveryHints::fromXRD($xrd)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
        // If there's an Hcard, let's grab its info
 | 
			
		||||
        if (array_key_exists('hcard', $hints)) {
 | 
			
		||||
            if (!array_key_exists('profileurl', $hints) ||
 | 
			
		||||
                        $hints['hcard'] != $hints['profileurl']) {
 | 
			
		||||
                $hcardHints = DiscoveryHints::fromHcardUrl($hints['hcard']);
 | 
			
		||||
                $hints = array_merge($hcardHints, $hints);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If we got a profile page, try that!
 | 
			
		||||
        $profileUrl = null;
 | 
			
		||||
        if (array_key_exists('profileurl', $hints)) {
 | 
			
		||||
            $profileUrl = $hints['profileurl'];
 | 
			
		||||
            try {
 | 
			
		||||
                common_log(LOG_INFO, "Discovery on acct:$addr with profile URL $profileUrl");
 | 
			
		||||
                $aprofile = self::fromUri($hints['profileurl']);
 | 
			
		||||
                self::cacheSet(sprintf('activitypub_profile:webfinger:%s', $addr), $aprofile->getUri());
 | 
			
		||||
                return $aprofile;
 | 
			
		||||
            } catch (Exception $e) {
 | 
			
		||||
                common_log(LOG_WARNING, "Failed creating profile from profile URL '$profileUrl': " . $e->getMessage());
 | 
			
		||||
                // keep looking
 | 
			
		||||
                                //
 | 
			
		||||
                                // @todo FIXME: This means an error discovering from profile page
 | 
			
		||||
                                // may give us a corrupt entry using the webfinger URI, which
 | 
			
		||||
                                // will obscure the correct page-keyed profile later on.
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // XXX: try hcard
 | 
			
		||||
        // XXX: try FOAF
 | 
			
		||||
 | 
			
		||||
        // TRANS: Exception. %s is a webfinger address.
 | 
			
		||||
        throw new Exception(sprintf(_m('Could not find a valid profile for "%s".'), $addr));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update remote user profile in local instance
 | 
			
		||||
     * Depends on do_update
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param array $res remote response
 | 
			
		||||
     * @return Profile remote Profile object
 | 
			
		||||
     */
 | 
			
		||||
    public static function update_profile($aprofile, $res)
 | 
			
		||||
    {
 | 
			
		||||
        // ActivityPub Profile
 | 
			
		||||
        $aprofile->uri            = $res['id'];
 | 
			
		||||
        $aprofile->nickname       = $res['preferredUsername'];
 | 
			
		||||
        $aprofile->fullname       = isset($res['name']) ? $res['name'] : null;
 | 
			
		||||
        $aprofile->bio            = isset($res['summary']) ? substr(strip_tags($res['summary']), 0, 1000) : null;
 | 
			
		||||
        $aprofile->inboxuri       = $res['inbox'];
 | 
			
		||||
        $aprofile->sharedInboxuri = isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : $res['inbox'];
 | 
			
		||||
 | 
			
		||||
        $profile = $aprofile->local_profile();
 | 
			
		||||
 | 
			
		||||
        $profile->modified = $aprofile->modified = common_sql_now();
 | 
			
		||||
 | 
			
		||||
        $fields = [
 | 
			
		||||
                    'uri'      => 'profileurl',
 | 
			
		||||
                    'nickname' => 'nickname',
 | 
			
		||||
                    'fullname' => 'fullname',
 | 
			
		||||
                    'bio'      => 'bio'
 | 
			
		||||
                    ];
 | 
			
		||||
 | 
			
		||||
        foreach ($fields as $af => $pf) {
 | 
			
		||||
            $profile->$pf = $aprofile->$af;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Profile
 | 
			
		||||
        $profile->update();
 | 
			
		||||
        $aprofile->update();
 | 
			
		||||
 | 
			
		||||
        // Public Key
 | 
			
		||||
        Activitypub_rsa::update_public_key($profile, $res['publicKey']['publicKeyPem']);
 | 
			
		||||
 | 
			
		||||
        // Avatar
 | 
			
		||||
        if (isset($res['icon']['url'])) {
 | 
			
		||||
            try {
 | 
			
		||||
                Activitypub_explorer::update_avatar($profile, $res['icon']['url']);
 | 
			
		||||
            } catch (Exception $e) {
 | 
			
		||||
                // Let the exception go, it isn't a serious issue
 | 
			
		||||
                common_debug('An error ocurred while grabbing remote avatar'.$e->getMessage());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $profile;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,178 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub Keys System
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_rsa extends Managed_DataObject
 | 
			
		||||
{
 | 
			
		||||
    public $__table = 'Activitypub_rsa';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return table definition for Schema setup and DB_DataObject usage.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @return array array of column definitions
 | 
			
		||||
     */
 | 
			
		||||
    public static function schemaDef()
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
                'fields' => [
 | 
			
		||||
                    'profile_id'  => ['type' => 'integer'],
 | 
			
		||||
                    'private_key' => ['type' => 'text'],
 | 
			
		||||
                    'public_key'  => ['type' => 'text', 'not null' => true],
 | 
			
		||||
                    'created'     => ['type' => 'datetime', 'not null' => true],
 | 
			
		||||
                    'modified'    => ['type' => 'datetime', 'not null' => true],
 | 
			
		||||
                ],
 | 
			
		||||
                'primary key' => ['profile_id'],
 | 
			
		||||
                'unique keys' => [
 | 
			
		||||
                    'Activitypub_rsa_profile_id_key'  => ['profile_id'],
 | 
			
		||||
                ],
 | 
			
		||||
                'foreign keys' => [
 | 
			
		||||
                    'Activitypub_profile_profile_id_fkey' => ['profile', ['profile_id' => 'id']],
 | 
			
		||||
                ],
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function get_private_key($profile)
 | 
			
		||||
    {
 | 
			
		||||
        $this->profile_id = $profile->getID();
 | 
			
		||||
        $apRSA = self::getKV('profile_id', $this->profile_id);
 | 
			
		||||
        if (!$apRSA instanceof Activitypub_rsa) {
 | 
			
		||||
            // No existing key pair for this profile
 | 
			
		||||
            if ($profile->isLocal()) {
 | 
			
		||||
                self::generate_keys($this->private_key, $this->public_key);
 | 
			
		||||
                $this->store_keys();
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new Exception('This is a remote Profile, there is no Private Key for this Profile.');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $apRSA->private_key;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Guarantees a Public Key for a given profile.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Profile $profile
 | 
			
		||||
     * @return string The public key
 | 
			
		||||
     * @throws ServerException It should never occur, but if so, we break everything!
 | 
			
		||||
     */
 | 
			
		||||
    public function ensure_public_key($profile, $fetch = true)
 | 
			
		||||
    {
 | 
			
		||||
        $this->profile_id = $profile->getID();
 | 
			
		||||
        $apRSA = self::getKV('profile_id', $this->profile_id);
 | 
			
		||||
        if (!$apRSA instanceof Activitypub_rsa) {
 | 
			
		||||
            // No existing key pair for this profile
 | 
			
		||||
            if ($profile->isLocal()) {
 | 
			
		||||
                self::generate_keys($this->private_key, $this->public_key);
 | 
			
		||||
                $this->store_keys();
 | 
			
		||||
            } else {
 | 
			
		||||
                // This should never happen, but try to recover!
 | 
			
		||||
                if ($fetch) {
 | 
			
		||||
                    $res = Activitypub_explorer::get_remote_user_activity(ActivityPubPlugin::actor_uri($profile));
 | 
			
		||||
                    Activitypub_rsa::update_public_key($profile, $res['publicKey']['publicKeyPem']);
 | 
			
		||||
                    return ensure_public_key($profile, false);
 | 
			
		||||
                } else {
 | 
			
		||||
                    throw new ServerException('Activitypub_rsa: Failed to find keys for given profile. That should have not happened!');
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $apRSA->public_key;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Insert the current object variables into the database.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @access public
 | 
			
		||||
     * @throws ServerException
 | 
			
		||||
     */
 | 
			
		||||
    public function store_keys()
 | 
			
		||||
    {
 | 
			
		||||
        $this->created = $this->modified = common_sql_now();
 | 
			
		||||
        $ok = $this->insert();
 | 
			
		||||
        if ($ok === false) {
 | 
			
		||||
            throw new ServerException('Cannot save ActivityPub RSA.');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates a pair of RSA keys.
 | 
			
		||||
     *
 | 
			
		||||
     * @author PHP Manual Contributed Notes <dirt@awoms.com>
 | 
			
		||||
     * @param string $private_key in/out
 | 
			
		||||
     * @param string $public_key in/out
 | 
			
		||||
     */
 | 
			
		||||
    public static function generate_keys(&$private_key, &$public_key)
 | 
			
		||||
    {
 | 
			
		||||
        $config = [
 | 
			
		||||
            'digest_alg'       => 'sha512',
 | 
			
		||||
            'private_key_bits' => 2048,
 | 
			
		||||
            'private_key_type' => OPENSSL_KEYTYPE_RSA,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        // Create the private and public key
 | 
			
		||||
        $res = openssl_pkey_new($config);
 | 
			
		||||
 | 
			
		||||
        // Extract the private key from $res to $private_key
 | 
			
		||||
        openssl_pkey_export($res, $private_key);
 | 
			
		||||
 | 
			
		||||
        // Extract the public key from $res to $pubKey
 | 
			
		||||
        $pubKey = openssl_pkey_get_details($res);
 | 
			
		||||
        $public_key = $pubKey["key"];
 | 
			
		||||
        unset($pubKey);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update public key.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Profile $profile
 | 
			
		||||
     * @param string $public_key
 | 
			
		||||
     */
 | 
			
		||||
    public static function update_public_key($profile, $public_key)
 | 
			
		||||
    {
 | 
			
		||||
        // Public Key
 | 
			
		||||
        $apRSA = new Activitypub_rsa();
 | 
			
		||||
        $apRSA->profile_id = $profile->getID();
 | 
			
		||||
        $apRSA->public_key = $public_key;
 | 
			
		||||
        $apRSA->modified = common_sql_now();
 | 
			
		||||
        if(!$apRSA->update()) {
 | 
			
		||||
            $apRSA->insert();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								classes/Activitypub_tag.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										18
									
								
								classes/Activitypub_tag.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -20,12 +20,13 @@
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -42,17 +43,22 @@ class Activitypub_tag extends Managed_DataObject
 | 
			
		||||
        /**
 | 
			
		||||
         * Generates a pretty tag from a Tag object
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @param Tag $tag
 | 
			
		||||
         * @return pretty array to be used in a response
 | 
			
		||||
         */
 | 
			
		||||
    public static function tag_to_array($tag)
 | 
			
		||||
        public static function tag_to_array ($tag)
 | 
			
		||||
        {
 | 
			
		||||
                $res = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
                '@context'          => [
 | 
			
		||||
                "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                [
 | 
			
		||||
                  "@language" => "en"
 | 
			
		||||
                ]
 | 
			
		||||
                ],
 | 
			
		||||
                'name' => $tag,
 | 
			
		||||
            'url'  => common_local_url('tag', ['tag' => $tag])
 | 
			
		||||
                'url'  => common_local_url ('tag', array('tag' => $tag))
 | 
			
		||||
                ];
 | 
			
		||||
 | 
			
		||||
                return $res;
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,89 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub error representation
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_undo extends Managed_DataObject
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates an ActivityPub representation of a Undo
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param array $object
 | 
			
		||||
     * @return pretty array to be used in a response
 | 
			
		||||
     */
 | 
			
		||||
    public static function undo_to_array($object)
 | 
			
		||||
    {
 | 
			
		||||
        $res = [
 | 
			
		||||
            '@context' => 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
            'id'     => $object['id'].'/undo',
 | 
			
		||||
            'type'   => 'Undo',
 | 
			
		||||
            'actor'  => $object['actor'],
 | 
			
		||||
            'object' => $object
 | 
			
		||||
        ];
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Verifies if a given object is acceptable for a Undo Activity.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Array $object
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public static function validate_object($object)
 | 
			
		||||
    {
 | 
			
		||||
        if (!is_array($object)) {
 | 
			
		||||
            throw new Exception('Invalid Object Format for Undo Activity.');
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($object['type'])) {
 | 
			
		||||
            throw new Exception('Object type was not specified for Undo Activity.');
 | 
			
		||||
        }
 | 
			
		||||
        switch ($object['type']) {
 | 
			
		||||
            case 'Follow':
 | 
			
		||||
            case 'Like':
 | 
			
		||||
                // Validate data
 | 
			
		||||
                if (!filter_var($object['object'], FILTER_VALIDATE_URL)) {
 | 
			
		||||
                    throw new Exception('Object is not a valid Object URI for Activity.');
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new Exception('This is not a supported Object Type for Undo Activity.');
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "dansup/activity-pub",
 | 
			
		||||
    "description": "ActivityPub plugin for GNU/Social",
 | 
			
		||||
    "type": "gnusocial-plugin",
 | 
			
		||||
    "require": {
 | 
			
		||||
        "pixelfed/http-signatures-guzzlehttp": "^4.0"
 | 
			
		||||
    },
 | 
			
		||||
    "license": "AGPL",
 | 
			
		||||
    "autoload": {
 | 
			
		||||
        "psr-4": {
 | 
			
		||||
            "Tests\\": "tests/"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "authors": [
 | 
			
		||||
        {
 | 
			
		||||
            "name": "Daniel Supernault",
 | 
			
		||||
            "email": "danielsupernault@gmail.com"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "Diogo Cordeiro",
 | 
			
		||||
            "email": "diogo@fc.up.pt"
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
@@ -1,104 +0,0 @@
 | 
			
		||||
#!/usr/bin/env php
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
define('INSTALLDIR', realpath(__DIR__ . '/../../..'));
 | 
			
		||||
 | 
			
		||||
$shortoptions = 'u:af';
 | 
			
		||||
$longoptions = ['uri=', 'all', 'force'];
 | 
			
		||||
 | 
			
		||||
$helptext = <<<END_OF_HELP
 | 
			
		||||
update_activitypub_profiles.php [options]
 | 
			
		||||
Refetch / update ActivityPub RSA keys, profile info and avatars. Useful if you
 | 
			
		||||
do something like accidentally delete your avatars directory when
 | 
			
		||||
you have no backup.
 | 
			
		||||
 | 
			
		||||
    -u --uri ActivityPub profile URI to update
 | 
			
		||||
    -a --all update all
 | 
			
		||||
 | 
			
		||||
END_OF_HELP;
 | 
			
		||||
 | 
			
		||||
require_once INSTALLDIR.'/scripts/commandline.inc';
 | 
			
		||||
 | 
			
		||||
$quiet = have_option('q', 'quiet');
 | 
			
		||||
 | 
			
		||||
if (!$quiet) {
 | 
			
		||||
    echo "ActivityPub Profiles updater will now start!\n";
 | 
			
		||||
    echo "Summoning Diogo Cordeiro, Richard Stallman and Chuck Norris to help us with this task!\n";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (have_option('u', 'uri')) {
 | 
			
		||||
    $uri = get_option_value('u', 'uri');
 | 
			
		||||
    $discovery = new Activitypub_explorer();
 | 
			
		||||
    $discovery = $discovery->lookup($uri);
 | 
			
		||||
    if (empty($discovery)) {
 | 
			
		||||
        echo "Bad URI\n";
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
    $user = $discovery->lookup($uri)[0];
 | 
			
		||||
    try {
 | 
			
		||||
        $res = Activitypub_explorer::get_remote_user_activity($uri);
 | 
			
		||||
    } catch (Exception $e) {
 | 
			
		||||
        echo $e->getMessage()."\n";
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
    if (!$quiet) {
 | 
			
		||||
        echo "Updated ".Activitypub_profile::update_profile($user, $res)->getBestName()."\n";
 | 
			
		||||
    }
 | 
			
		||||
} else if (!have_option('a', 'all')) {
 | 
			
		||||
    show_help();
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$user = new Activitypub_profile();
 | 
			
		||||
$cnt = $user->find();
 | 
			
		||||
if (!empty($cnt)) {
 | 
			
		||||
    if (!$quiet) {
 | 
			
		||||
        echo "Found {$cnt} ActivityPub profiles:\n";
 | 
			
		||||
    }
 | 
			
		||||
} else {
 | 
			
		||||
    if (have_option('u', 'uri')) {
 | 
			
		||||
        if (!$quiet) {
 | 
			
		||||
            echo "Couldn't find an existing ActivityPub profile with that URI.\n";
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        if (!$quiet) {
 | 
			
		||||
            echo "Couldn't find any existing ActivityPub profiles.\n";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    exit(0);
 | 
			
		||||
}
 | 
			
		||||
while ($user->fetch()) {
 | 
			
		||||
    try {
 | 
			
		||||
        $res = Activitypub_explorer::get_remote_user_activity($user->uri);
 | 
			
		||||
        if (!$quiet) {
 | 
			
		||||
            echo "Updated ".Activitypub_profile::update_profile($user, $res)->getBestName()."\n";
 | 
			
		||||
        }
 | 
			
		||||
    } catch (Exception $e) {
 | 
			
		||||
        // let it go
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								phpunit.xml
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								phpunit.xml
									
									
									
									
									
								
							@@ -1,25 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<phpunit backupGlobals="false"
 | 
			
		||||
         backupStaticAttributes="false"
 | 
			
		||||
         bootstrap="vendor/autoload.php"
 | 
			
		||||
         colors="true"
 | 
			
		||||
         convertErrorsToExceptions="true"
 | 
			
		||||
         convertNoticesToExceptions="true"
 | 
			
		||||
         convertWarningsToExceptions="true"
 | 
			
		||||
         processIsolation="false"
 | 
			
		||||
         stopOnFailure="false">
 | 
			
		||||
    <testsuites>
 | 
			
		||||
        <testsuite name="Feature">
 | 
			
		||||
            <directory suffix="Test.php">./tests/Feature</directory>
 | 
			
		||||
        </testsuite>
 | 
			
		||||
 | 
			
		||||
        <testsuite name="Unit">
 | 
			
		||||
            <directory suffix="Test.php">./tests/Unit</directory>
 | 
			
		||||
        </testsuite>
 | 
			
		||||
    </testsuites>
 | 
			
		||||
    <filter>
 | 
			
		||||
        <whitelist processUncoveredFilesFromWhitelist="true">
 | 
			
		||||
            <directory suffix=".php">./app</directory>
 | 
			
		||||
        </whitelist>
 | 
			
		||||
    </filter>
 | 
			
		||||
</phpunit>
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests;
 | 
			
		||||
 | 
			
		||||
trait CreatesApplication
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the application.
 | 
			
		||||
     *
 | 
			
		||||
     * @return todo
 | 
			
		||||
     */
 | 
			
		||||
    public static function createApplication()
 | 
			
		||||
    {
 | 
			
		||||
        if (!defined('INSTALLDIR')) {
 | 
			
		||||
            define('INSTALLDIR', __DIR__ . '/../../../');
 | 
			
		||||
        }
 | 
			
		||||
        if (!defined('GNUSOCIAL')) {
 | 
			
		||||
            define('GNUSOCIAL', true);
 | 
			
		||||
        }
 | 
			
		||||
        if (!defined('STATUSNET')) {
 | 
			
		||||
            define('STATUSNET', true);  // compatibility
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        require INSTALLDIR . '/lib/common.php';
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests;
 | 
			
		||||
 | 
			
		||||
use PHPUnit\Framework\TestCase as BaseTestCase;
 | 
			
		||||
 | 
			
		||||
abstract class TestCase extends BaseTestCase
 | 
			
		||||
{
 | 
			
		||||
    use CreatesApplication;
 | 
			
		||||
 | 
			
		||||
    protected function setUp()
 | 
			
		||||
    {
 | 
			
		||||
        $this->createApplication();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,43 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
require 'AcceptHeader.php';
 | 
			
		||||
 | 
			
		||||
class ContainerTest extends \PHPUnit_Framework_TestCase
 | 
			
		||||
{
 | 
			
		||||
    public function testHeader1()
 | 
			
		||||
    {
 | 
			
		||||
        $acceptHeader = new AcceptHeader('audio/*; q=0.2, audio/basic');
 | 
			
		||||
        $this->assertEquals('audio/basic', $this->_getMedia($acceptHeader[0]));
 | 
			
		||||
        $this->assertEquals('audio/*; q=0.2', $this->_getMedia($acceptHeader[1]));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testHeader2()
 | 
			
		||||
    {
 | 
			
		||||
        $acceptHeader = new AcceptHeader('text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5');
 | 
			
		||||
        $this->assertEquals('text/html; level=1', $this->_getMedia($acceptHeader[0]));
 | 
			
		||||
        $this->assertEquals('text/html; q=0.7', $this->_getMedia($acceptHeader[1]));
 | 
			
		||||
        $this->assertEquals('*/*; q=0.5', $this->_getMedia($acceptHeader[2]));
 | 
			
		||||
        $this->assertEquals('text/html; level=2; q=0.4', $this->_getMedia($acceptHeader[3]));
 | 
			
		||||
        $this->assertEquals('text/*; q=0.3', $this->_getMedia($acceptHeader[4]));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testHeader3()
 | 
			
		||||
    {
 | 
			
		||||
        $acceptHeader = new AcceptHeader('text/*, text/html, text/html;level=1, */*');
 | 
			
		||||
        $this->assertEquals('text/html; level=1', $this->_getMedia($acceptHeader[0]));
 | 
			
		||||
        $this->assertEquals('text/html', $this->_getMedia($acceptHeader[1]));
 | 
			
		||||
        $this->assertEquals('text/*', $this->_getMedia($acceptHeader[2]));
 | 
			
		||||
        $this->assertEquals('*/*', $this->_getMedia($acceptHeader[3]));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function _getMedia(array $mediaType)
 | 
			
		||||
    {
 | 
			
		||||
        $str = $mediaType['type'] . '/' . $mediaType['subtype'];
 | 
			
		||||
        if (!empty($mediaType['params'])) {
 | 
			
		||||
            foreach ($mediaType['params'] as $k => $v) {
 | 
			
		||||
                $str .= '; ' . $k . '=' . $v;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $str;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,159 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests\Unit;
 | 
			
		||||
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
 | 
			
		||||
class ProfileObjectTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    public function testLibraryInstalled()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertTrue(class_exists('\Activitypub_profile'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testActivitypubProfile()
 | 
			
		||||
    {
 | 
			
		||||
        // Mimic proper ACCEPT header
 | 
			
		||||
        $_SERVER['HTTP_ACCEPT'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams';
 | 
			
		||||
 | 
			
		||||
        /* Test do_insert() */
 | 
			
		||||
        $aprofile = new \Activitypub_profile();
 | 
			
		||||
        $aprofile->uri = 'https://testinstance.net/index.php/user/1';
 | 
			
		||||
        $aprofile->nickname = 'test1';
 | 
			
		||||
        $aprofile->fullname = 'Test User 1';
 | 
			
		||||
        $aprofile->bio      = 'I am a nice test 1 guy';
 | 
			
		||||
        $aprofile->inboxuri = "https://testinstance.net/index.php/user/1/inbox.json";
 | 
			
		||||
        $aprofile->sharedInboxuri = "https://testinstance.net/inbox.json";
 | 
			
		||||
        $aprofile->do_insert();
 | 
			
		||||
 | 
			
		||||
        /* Test local_profile() */
 | 
			
		||||
        $profile = $aprofile->local_profile();
 | 
			
		||||
 | 
			
		||||
        /* Test from_profile() and create_from_local_profile() */
 | 
			
		||||
        $this->assertTrue($this->compare_aprofiles($aprofile, \Activitypub_profile::from_profile($profile)));
 | 
			
		||||
 | 
			
		||||
        /* Create Keys for Test User 1 */
 | 
			
		||||
        $apRSA = new \Activitypub_rsa();
 | 
			
		||||
        $apRSA->profile_id = $profile->getID();
 | 
			
		||||
        \Activitypub_rsa::generate_keys($apRSA->private_key, $apRSA->public_key);
 | 
			
		||||
        $apRSA->store_keys();
 | 
			
		||||
 | 
			
		||||
        /* Test profile_to_array() */
 | 
			
		||||
        // Fetch ActivityPub Actor Object representation
 | 
			
		||||
        $profile_array = \Activitypub_profile::profile_to_array($profile);
 | 
			
		||||
        // Check type
 | 
			
		||||
        $this->assertTrue(is_array($profile_array));
 | 
			
		||||
        // Test with Explorer's Profile Tester
 | 
			
		||||
        $this->assertTrue(\Activitypub_explorer::validate_remote_response($profile_array));
 | 
			
		||||
 | 
			
		||||
        /* Test get_inbox() */
 | 
			
		||||
        $this->assertTrue($aprofile->sharedInboxuri == $aprofile->get_inbox());
 | 
			
		||||
 | 
			
		||||
        /* Test getUri() */
 | 
			
		||||
        $this->assertTrue($aprofile->uri == $aprofile->getUri());
 | 
			
		||||
 | 
			
		||||
        /* Test getUrl() */
 | 
			
		||||
        $this->assertTrue($profile->getUrl() == $aprofile->getUrl());
 | 
			
		||||
 | 
			
		||||
        /* Test getID() */
 | 
			
		||||
        $this->assertTrue($profile->getID() == $aprofile->getID());
 | 
			
		||||
 | 
			
		||||
        /* Test fromUri() */
 | 
			
		||||
        $this->assertTrue($this->compare_aprofiles($aprofile, \Activitypub_profile::fromUri($aprofile->uri)));
 | 
			
		||||
 | 
			
		||||
        /* Remove Remote User Test 1 */
 | 
			
		||||
        $old_id = $profile->getID();
 | 
			
		||||
        $apRSA->delete();
 | 
			
		||||
        $aprofile->delete();
 | 
			
		||||
        $profile->delete();
 | 
			
		||||
        // Check if successfuly removed
 | 
			
		||||
        try {
 | 
			
		||||
            \Profile::getById($old_id);
 | 
			
		||||
            $this->assertTrue(false);
 | 
			
		||||
        } catch (\NoResultException $e) {
 | 
			
		||||
            $this->assertTrue(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* Test ensure_web_finger() */
 | 
			
		||||
        // TODO: Maybe elaborate on this function's tests
 | 
			
		||||
        try {
 | 
			
		||||
            \Activitypub_profile::ensure_web_finger('test1@testinstance.net');
 | 
			
		||||
            $this->assertTrue(false);
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            $this->assertTrue($e->getMessage() == 'Not a valid webfinger address.' ||
 | 
			
		||||
                              $e->getMessage() == 'Not a valid webfinger address (via cache).');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Helpers
 | 
			
		||||
 | 
			
		||||
    private function compare_profiles(\Profile $a, \Profile $b)
 | 
			
		||||
    {
 | 
			
		||||
        if (($av = $a->getID()) != ($bv = $b->getID())) {
 | 
			
		||||
            throw new Exception('Compare Profiles 1 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->getNickname()) != ($bv = $b->getNickname())) {
 | 
			
		||||
            throw new Exception('Compare Profiles 2 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->getFullname()) != ($bv = $b->getFullname())) {
 | 
			
		||||
            throw new Exception('Compare Profiles 3 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->getUrl()) != ($bv = $b->getUrl())) {
 | 
			
		||||
            throw new Exception('Compare Profiles 4 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->getDescription()) != ($bv = $b->getDescription())) {
 | 
			
		||||
            throw new Exception('Compare Profiles 5 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->getLocation()) != ($bv = $b->getLocation())) {
 | 
			
		||||
            throw new Exception('Compare Profiles 6 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->getNickname()) != ($bv = $b->getNickname())) {
 | 
			
		||||
            throw new Exception('Compare Profiles 7 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->lat) != ($bv = $b->lat)) {
 | 
			
		||||
            throw new Exception('Compare Profiles 8 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->lon) != ($bv = $b->lon)) {
 | 
			
		||||
            throw new Exception('Compare Profiles 9 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function compare_aprofiles(\Activitypub_profile $a, \Activitypub_profile $b)
 | 
			
		||||
    {
 | 
			
		||||
        if (($av = $a->getUri()) != ($bv = $b->getUri())) {
 | 
			
		||||
            throw new Exception('Compare AProfiles 1 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->getUrl()) != ($bv = $b->getUrl())) {
 | 
			
		||||
            throw new Exception('Compare AProfiles 2 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->getID()) != ($bv = $b->getID())) {
 | 
			
		||||
            throw new Exception('Compare AProfiles 3 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->profile_id) != ($bv = $b->profile_id)) {
 | 
			
		||||
            throw new Exception('Compare AProfiles 4 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->inboxuri) != ($bv = $b->inboxuri)) {
 | 
			
		||||
            throw new Exception('Compare AProfiles 5 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (($av = $a->sharedInboxuri) != ($bv = $b->sharedInboxuri)) {
 | 
			
		||||
            throw new Exception('Compare AProfiles 6 Fail: $a: '.$av.' is different from $b: '.$bv);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests\Unit;
 | 
			
		||||
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
 | 
			
		||||
class ExampleTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * A basic test example.
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function testBasicTest()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertTrue(true);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,141 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Tests\Unit;
 | 
			
		||||
 | 
			
		||||
use Tests\TestCase;
 | 
			
		||||
use GuzzleHttp\Client;
 | 
			
		||||
use GuzzleHttp\Handler\CurlHandler;
 | 
			
		||||
use GuzzleHttp\Handler\MockHandler;
 | 
			
		||||
use GuzzleHttp\HandlerStack;
 | 
			
		||||
use GuzzleHttp\Middleware;
 | 
			
		||||
use GuzzleHttp\Psr7\Request;
 | 
			
		||||
use GuzzleHttp\Psr7\Response;
 | 
			
		||||
use HttpSignatures\Context;
 | 
			
		||||
use HttpSignatures\GuzzleHttpSignatures;
 | 
			
		||||
 | 
			
		||||
class HTTPSignatureTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Context
 | 
			
		||||
     */
 | 
			
		||||
    private $context;
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Client
 | 
			
		||||
     */
 | 
			
		||||
    private $client;
 | 
			
		||||
    /**
 | 
			
		||||
     * @var
 | 
			
		||||
     */
 | 
			
		||||
    private $history = [];
 | 
			
		||||
 | 
			
		||||
    public function testLibraryInstalled()
 | 
			
		||||
    {
 | 
			
		||||
        $this->assertTrue(class_exists('\GuzzleHttp\Client'));
 | 
			
		||||
        $this->assertTrue(class_exists('\HttpSignatures\Context'));
 | 
			
		||||
        $this->assertTrue(class_exists('\HttpSignatures\GuzzleHttpSignatures'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setUp()
 | 
			
		||||
    {
 | 
			
		||||
        $this->context = new Context([
 | 
			
		||||
            'keys' => ['pda' => 'secret'],
 | 
			
		||||
            'algorithm' => 'hmac-sha256',
 | 
			
		||||
            'headers' => ['(request-target)', 'date'],
 | 
			
		||||
        ]);
 | 
			
		||||
        $stack = new HandlerStack();
 | 
			
		||||
        $stack->setHandler(new MockHandler([
 | 
			
		||||
            new Response(200, ['Content-Length' => 0]),
 | 
			
		||||
        ]));
 | 
			
		||||
        $stack->push(GuzzleHttpSignatures::middlewareFromContext($this->context));
 | 
			
		||||
        $stack->push(Middleware::history($this->history));
 | 
			
		||||
        $this->client = new Client(['handler' => $stack]);
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * test signing a message
 | 
			
		||||
     */
 | 
			
		||||
    public function testGuzzleRequestHasExpectedHeaders()
 | 
			
		||||
    {
 | 
			
		||||
        $this->client->get('/path?query=123', [
 | 
			
		||||
            'headers' => ['date' => 'today', 'accept' => 'llamas']
 | 
			
		||||
        ]);
 | 
			
		||||
        // get last request
 | 
			
		||||
        $message = end($this->history);
 | 
			
		||||
        /** @var Request $request */
 | 
			
		||||
        $request = $message['request'];
 | 
			
		||||
        /** @var Response $response */
 | 
			
		||||
        $response = $message['request'];
 | 
			
		||||
        $expectedString = implode(
 | 
			
		||||
            ',',
 | 
			
		||||
            [
 | 
			
		||||
                'keyId="pda"',
 | 
			
		||||
                'algorithm="hmac-sha256"',
 | 
			
		||||
                'headers="(request-target) date"',
 | 
			
		||||
                'signature="SFlytCGpsqb/9qYaKCQklGDvwgmrwfIERFnwt+yqPJw="',
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
        $this->assertEquals(
 | 
			
		||||
            [$expectedString],
 | 
			
		||||
            $request->getHeader('Signature')
 | 
			
		||||
        );
 | 
			
		||||
        $this->assertEquals(
 | 
			
		||||
            ['Signature ' . $expectedString],
 | 
			
		||||
            $request->getHeader('Authorization')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * test signing a message with a URL that doesn't contain a ?query
 | 
			
		||||
     */
 | 
			
		||||
    public function testGuzzleRequestHasExpectedHeaders2()
 | 
			
		||||
    {
 | 
			
		||||
        $this->client->get('/path', [
 | 
			
		||||
            'headers' => ['date' => 'today', 'accept' => 'llamas']
 | 
			
		||||
        ]);
 | 
			
		||||
        // get last request
 | 
			
		||||
        $message = end($this->history);
 | 
			
		||||
        /** @var Request $request */
 | 
			
		||||
        $request = $message['request'];
 | 
			
		||||
        /** @var Response $response */
 | 
			
		||||
        $response = $message['request'];
 | 
			
		||||
        $expectedString = implode(
 | 
			
		||||
            ',',
 | 
			
		||||
            [
 | 
			
		||||
                'keyId="pda"',
 | 
			
		||||
                'algorithm="hmac-sha256"',
 | 
			
		||||
                'headers="(request-target) date"',
 | 
			
		||||
                'signature="DAtF133khP05pS5Gh8f+zF/UF7mVUojMj7iJZO3Xk4o="',
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
        $this->assertEquals(
 | 
			
		||||
            [$expectedString],
 | 
			
		||||
            $request->getHeader('Signature')
 | 
			
		||||
        );
 | 
			
		||||
        $this->assertEquals(
 | 
			
		||||
            ['Signature ' . $expectedString],
 | 
			
		||||
            $request->getHeader('Authorization')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    public function getVerifyGuzzleRequestVectors()
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            /* path, headers */
 | 
			
		||||
            ['/path?query=123', ['date' => 'today', 'accept' => 'llamas']],
 | 
			
		||||
            ['/path?z=zebra&a=antelope', ['date' => 'today']],
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * @dataProvider getVerifyGuzzleRequestVectors
 | 
			
		||||
     * @param string $path
 | 
			
		||||
     * @param array $headers
 | 
			
		||||
     */
 | 
			
		||||
    public function testVerifyGuzzleRequest($path, $headers)
 | 
			
		||||
    {
 | 
			
		||||
        $this->client->get($path, ['headers' => $headers]);
 | 
			
		||||
        // get last request
 | 
			
		||||
        $message = end($this->history);
 | 
			
		||||
        /** @var Request $request */
 | 
			
		||||
        $request = $message['request'];
 | 
			
		||||
        /** @var Response $response */
 | 
			
		||||
        $response = $message['request'];
 | 
			
		||||
        $this->assertTrue($this->context->verifier()->isValid($request));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,116 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * Note : Code is released under the GNU LGPL
 | 
			
		||||
 *
 | 
			
		||||
 * Please do not change the header of this file
 | 
			
		||||
 *
 | 
			
		||||
 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU
 | 
			
		||||
 * Lesser General Public License as published by the Free Software Foundation; either version 2 of
 | 
			
		||||
 * the License, or (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 | 
			
		||||
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | 
			
		||||
 *
 | 
			
		||||
 * See the GNU Lesser General Public License for more details.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The AcceptHeader page will parse and sort the different
 | 
			
		||||
 * allowed types for the content negociations
 | 
			
		||||
 *
 | 
			
		||||
 * @author Pierrick Charron <pierrick@webstart.fr>
 | 
			
		||||
 */
 | 
			
		||||
class AcceptHeader extends \ArrayObject
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $header Value of the Accept header
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct($header)
 | 
			
		||||
    {
 | 
			
		||||
        $acceptedTypes = $this->_parse($header);
 | 
			
		||||
        usort($acceptedTypes, [$this, '_compare']);
 | 
			
		||||
        parent::__construct($acceptedTypes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Parse the accept header and return an array containing
 | 
			
		||||
     * all the informations about the Accepted types
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $header Value of the Accept header
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    private function _parse($data)
 | 
			
		||||
    {
 | 
			
		||||
        $array = [];
 | 
			
		||||
        $items = explode(',', $data);
 | 
			
		||||
        foreach ($items as $item) {
 | 
			
		||||
            $elems = explode(';', $item);
 | 
			
		||||
 | 
			
		||||
            $acceptElement = [];
 | 
			
		||||
            $mime = current($elems);
 | 
			
		||||
            list($type, $subtype) = explode('/', $mime);
 | 
			
		||||
            $acceptElement['type'] = trim($type);
 | 
			
		||||
            $acceptElement['subtype'] = trim($subtype);
 | 
			
		||||
            $acceptElement['raw'] = $mime;
 | 
			
		||||
 | 
			
		||||
            $acceptElement['params'] = [];
 | 
			
		||||
            while (next($elems)) {
 | 
			
		||||
                list($name, $value) = explode('=', current($elems));
 | 
			
		||||
                $acceptElement['params'][trim($name)] = trim($value);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $array[] = $acceptElement;
 | 
			
		||||
        }
 | 
			
		||||
        return $array;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Compare two Accepted types with their parameters to know
 | 
			
		||||
     * if one media type should be used instead of an other
 | 
			
		||||
     *
 | 
			
		||||
     * @param array $a The first media type and its parameters
 | 
			
		||||
     * @param array $b The second media type and its parameters
 | 
			
		||||
     * @return int
 | 
			
		||||
     */
 | 
			
		||||
    private function _compare($a, $b)
 | 
			
		||||
    {
 | 
			
		||||
        $a_q = isset($a['params']['q']) ? floatval($a['params']['q']) : 1.0;
 | 
			
		||||
        $b_q = isset($b['params']['q']) ? floatval($b['params']['q']) : 1.0;
 | 
			
		||||
        if ($a_q === $b_q) {
 | 
			
		||||
            $a_count = count($a['params']);
 | 
			
		||||
            $b_count = count($b['params']);
 | 
			
		||||
            if ($a_count === $b_count) {
 | 
			
		||||
                if ($r = $this->_compareSubType($a['subtype'], $b['subtype'])) {
 | 
			
		||||
                    return $r;
 | 
			
		||||
                } else {
 | 
			
		||||
                    return $this->_compareSubType($a['type'], $b['type']);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                return $a_count < $b_count;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            return $a_q < $b_q;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Compare two subtypes
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $a First subtype to compare
 | 
			
		||||
     * @param string $b Second subtype to compare
 | 
			
		||||
     * @return int
 | 
			
		||||
     */
 | 
			
		||||
    private function _compareSubType($a, $b)
 | 
			
		||||
    {
 | 
			
		||||
        if ($a === '*' && $b !== '*') {
 | 
			
		||||
            return 1;
 | 
			
		||||
        } elseif ($b === '*' && $a !== '*') {
 | 
			
		||||
            return -1;
 | 
			
		||||
        } else {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,101 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Utility class to hold a bunch of constant defining default verb types
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_activityverb2 extends Managed_DataObject
 | 
			
		||||
{
 | 
			
		||||
    const FULL_LIST =
 | 
			
		||||
    [
 | 
			
		||||
        'Accept'          => 'https://www.w3.org/ns/activitystreams#Accept',
 | 
			
		||||
        'TentativeAccept' => 'https://www.w3.org/ns/activitystreams#TentativeAccept',
 | 
			
		||||
        'Add'             => 'https://www.w3.org/ns/activitystreams#Add',
 | 
			
		||||
        'Arrive'          => 'https://www.w3.org/ns/activitystreams#Arrive',
 | 
			
		||||
        'Create'          => 'https://www.w3.org/ns/activitystreams#Create',
 | 
			
		||||
        'Delete'          => 'https://www.w3.org/ns/activitystreams#Delete',
 | 
			
		||||
        'Follow'          => 'https://www.w3.org/ns/activitystreams#Follow',
 | 
			
		||||
        'Ignore'          => 'https://www.w3.org/ns/activitystreams#Ignore',
 | 
			
		||||
        'Join'            => 'https://www.w3.org/ns/activitystreams#Join',
 | 
			
		||||
        'Leave'           => 'https://www.w3.org/ns/activitystreams#Leave',
 | 
			
		||||
        'Like'            => 'https://www.w3.org/ns/activitystreams#Like',
 | 
			
		||||
        'Offer'           => 'https://www.w3.org/ns/activitystreams#Offer',
 | 
			
		||||
        'Invite'          => 'https://www.w3.org/ns/activitystreams#Invite',
 | 
			
		||||
        'Reject'          => 'https://www.w3.org/ns/activitystreams#Reject',
 | 
			
		||||
        'TentativeReject' => 'https://www.w3.org/ns/activitystreams#TentativeReject',
 | 
			
		||||
        'Remove'          => 'https://www.w3.org/ns/activitystreams#Remove',
 | 
			
		||||
        'Undo'            => 'https://www.w3.org/ns/activitystreams#Undo',
 | 
			
		||||
        'Update'          => 'https://www.w3.org/ns/activitystreams#Update',
 | 
			
		||||
        'View'            => 'https://www.w3.org/ns/activitystreams#View',
 | 
			
		||||
        'Listen'          => 'https://www.w3.org/ns/activitystreams#Listen',
 | 
			
		||||
        'Read'            => 'https://www.w3.org/ns/activitystreams#Read',
 | 
			
		||||
        'Move'            => 'https://www.w3.org/ns/activitystreams#Move',
 | 
			
		||||
        'Travel'          => 'https://www.w3.org/ns/activitystreams#Travel',
 | 
			
		||||
        'Announce'        => 'https://www.w3.org/ns/activitystreams#Announce',
 | 
			
		||||
        'Block'           => 'https://www.w3.org/ns/activitystreams#Block',
 | 
			
		||||
        'Flag'            => 'https://www.w3.org/ns/activitystreams#Flag',
 | 
			
		||||
        'Dislike'         => 'https://www.w3.org/ns/activitystreams#Dislike',
 | 
			
		||||
        'Question'        => 'https://www.w3.org/ns/activitystreams#Question'
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    const KNOWN =
 | 
			
		||||
    [
 | 
			
		||||
        'Accept',
 | 
			
		||||
        'Create',
 | 
			
		||||
        'Delete',
 | 
			
		||||
        'Follow',
 | 
			
		||||
        'Like',
 | 
			
		||||
        'Undo',
 | 
			
		||||
        'Announce'
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts canonical into verb.
 | 
			
		||||
     *
 | 
			
		||||
     * @author GNU Social
 | 
			
		||||
     * @param string $verb
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public static function canonical($verb)
 | 
			
		||||
    {
 | 
			
		||||
        $ns = 'https://www.w3.org/ns/activitystreams#';
 | 
			
		||||
        if (substr($verb, 0, mb_strlen($ns)) == $ns) {
 | 
			
		||||
            return substr($verb, mb_strlen($ns));
 | 
			
		||||
        } else {
 | 
			
		||||
            return $verb;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,157 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * Some utilities for generating hint data
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    GNUsocial
 | 
			
		||||
 * @copyright 2010 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class DiscoveryHints
 | 
			
		||||
{
 | 
			
		||||
    public static function fromXRD(XML_XRD $xrd)
 | 
			
		||||
    {
 | 
			
		||||
        $hints = [];
 | 
			
		||||
 | 
			
		||||
        if (Event::handle('StartDiscoveryHintsFromXRD', [$xrd, &$hints])) {
 | 
			
		||||
            foreach ($xrd->links as $link) {
 | 
			
		||||
                switch ($link->rel) {
 | 
			
		||||
                case WebFingerResource_Profile::PROFILEPAGE:
 | 
			
		||||
                    $hints['profileurl'] = $link->href;
 | 
			
		||||
                    break;
 | 
			
		||||
                    $hints['salmon'] = $link->href;
 | 
			
		||||
                    break;
 | 
			
		||||
                case Discovery::UPDATESFROM:
 | 
			
		||||
                    if (empty($link->type) || $link->type == 'application/atom+xml') {
 | 
			
		||||
                        $hints['feedurl'] = $link->href;
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                case Discovery::HCARD:
 | 
			
		||||
                case Discovery::MF2_HCARD:
 | 
			
		||||
                    $hints['hcard'] = $link->href;
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Event::handle('EndDiscoveryHintsFromXRD', [$xrd, &$hints]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $hints;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function fromHcardUrl($url)
 | 
			
		||||
    {
 | 
			
		||||
        $client = new HTTPClient();
 | 
			
		||||
        $client->setHeader('Accept', 'text/html,application/xhtml+xml');
 | 
			
		||||
        try {
 | 
			
		||||
            $response = $client->get($url);
 | 
			
		||||
 | 
			
		||||
            if (!$response->isOk()) {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
        } catch (HTTP_Request2_Exception $e) {
 | 
			
		||||
            // Any HTTPClient error that might've been thrown
 | 
			
		||||
            common_log(LOG_ERR, __METHOD__ . ':'.$e->getMessage());
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return self::hcardHints(
 | 
			
		||||
            $response->getBody(),
 | 
			
		||||
                                $response->getEffectiveUrl()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function hcardHints($body, $url)
 | 
			
		||||
    {
 | 
			
		||||
        $hcard = self::_hcard($body, $url);
 | 
			
		||||
 | 
			
		||||
        if (empty($hcard)) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $hints = [];
 | 
			
		||||
 | 
			
		||||
        // XXX: don't copy stuff into an array and then copy it again
 | 
			
		||||
 | 
			
		||||
        if (array_key_exists('nickname', $hcard) && !empty($hcard['nickname'][0])) {
 | 
			
		||||
            $hints['nickname'] = $hcard['nickname'][0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (array_key_exists('name', $hcard) && !empty($hcard['name'][0])) {
 | 
			
		||||
            $hints['fullname'] = $hcard['name'][0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (array_key_exists('photo', $hcard) && count($hcard['photo'])) {
 | 
			
		||||
            $hints['avatar'] = $hcard['photo'][0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (array_key_exists('note', $hcard) && !empty($hcard['note'][0])) {
 | 
			
		||||
            $hints['bio'] = $hcard['note'][0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (array_key_exists('adr', $hcard) && !empty($hcard['adr'][0])) {
 | 
			
		||||
            $hints['location'] = $hcard['adr'][0]['value'];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (array_key_exists('url', $hcard) && !empty($hcard['url'][0])) {
 | 
			
		||||
            $hints['homepage'] = $hcard['url'][0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $hints;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function _hcard($body, $url)
 | 
			
		||||
    {
 | 
			
		||||
        $mf2 = new Mf2\Parser($body, $url);
 | 
			
		||||
        $mf2 = $mf2->parse();
 | 
			
		||||
 | 
			
		||||
        if (empty($mf2['items'])) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $hcards = [];
 | 
			
		||||
 | 
			
		||||
        foreach ($mf2['items'] as $item) {
 | 
			
		||||
            if (!in_array('h-card', $item['type'])) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // We found a match, return it immediately
 | 
			
		||||
            if (isset($item['properties']['url']) && in_array($url, $item['properties']['url'])) {
 | 
			
		||||
                return $item['properties'];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Let's keep all the hcards for later, to return one of them at least
 | 
			
		||||
            $hcards[] = $item['properties'];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // No match immediately for the url we expected, but there were h-cards found
 | 
			
		||||
        if (count($hcards) > 0) {
 | 
			
		||||
            return $hcards[0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										385
									
								
								utils/explorer.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										385
									
								
								utils/explorer.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -20,18 +20,19 @@
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub's own Explorer
 | 
			
		||||
 *
 | 
			
		||||
 * Allows to discovery new (or the same) Profiles (both local or remote)
 | 
			
		||||
 * Allows to discovery new (or the same) ActivityPub profiles
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
@@ -41,46 +42,21 @@ if (!defined('GNUSOCIAL')) {
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_explorer
 | 
			
		||||
{
 | 
			
		||||
    private $discovered_actor_profiles = [];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shortcut function to get a single profile from its URL.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param string $url
 | 
			
		||||
     * @return Profile
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public static function get_profile_from_url($url)
 | 
			
		||||
    {
 | 
			
		||||
        $discovery = new Activitypub_explorer;
 | 
			
		||||
        // Get valid Actor object
 | 
			
		||||
        $actor_profile = $discovery->lookup($url);
 | 
			
		||||
        if (!empty($actor_profile)) {
 | 
			
		||||
            return $actor_profile[0];
 | 
			
		||||
        }
 | 
			
		||||
        throw new Exception('Invalid Actor.');
 | 
			
		||||
    }
 | 
			
		||||
        private $discovered_actor_profiles = array ();
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get every profile from the given URL
 | 
			
		||||
         * This function cleans the $this->discovered_actor_profiles array
 | 
			
		||||
         * so that there is no erroneous data
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @param string $url User's url
 | 
			
		||||
         * @return array of Profile objects
 | 
			
		||||
         */
 | 
			
		||||
    public function lookup($url)
 | 
			
		||||
        public function lookup ($url)
 | 
			
		||||
        {
 | 
			
		||||
        if (in_array($url, ACTIVITYPUB_PUBLIC_TO)) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
                $this->discovered_actor_profiles = array ();
 | 
			
		||||
 | 
			
		||||
        common_debug('ActivityPub Explorer: Started now looking for '.$url);
 | 
			
		||||
        $this->discovered_actor_profiles = [];
 | 
			
		||||
 | 
			
		||||
        return $this->_lookup($url);
 | 
			
		||||
                return $this->_lookup ($url);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
@@ -88,105 +64,51 @@ class Activitypub_explorer
 | 
			
		||||
         * This is a recursive function that will accumulate the results on
 | 
			
		||||
         * $discovered_actor_profiles array
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @param string $url User's url
 | 
			
		||||
         * @return array of Profile objects
 | 
			
		||||
         */
 | 
			
		||||
    private function _lookup($url)
 | 
			
		||||
        private function _lookup ($url)
 | 
			
		||||
        {
 | 
			
		||||
                // First check if we already have it locally and, if so, return it
 | 
			
		||||
                // If the local fetch fails: grab it remotely, store locally and return
 | 
			
		||||
        if (! ($this->grab_local_user($url) || $this->grab_remote_user($url))) {
 | 
			
		||||
            throw new Exception('User not found.');
 | 
			
		||||
                if (! ($this->grab_local_user ($url) || $this->grab_remote_user ($url))) {
 | 
			
		||||
                    throw new Exception ("User not found");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                return $this->discovered_actor_profiles;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
     * This ensures that we are using a valid ActivityPub URI
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param string $url
 | 
			
		||||
     * @return boolean success state (related to the response)
 | 
			
		||||
     * @throws Exception (If the HTTP request fails)
 | 
			
		||||
     */
 | 
			
		||||
    private function ensure_proper_remote_uri($url)
 | 
			
		||||
    {
 | 
			
		||||
        $client    = new HTTPClient();
 | 
			
		||||
        $headers   = [];
 | 
			
		||||
        $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
 | 
			
		||||
        $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
 | 
			
		||||
        $response  = $client->get($url, $headers);
 | 
			
		||||
        $res = json_decode($response->getBody(), true);
 | 
			
		||||
        if (self::validate_remote_response($res)) {
 | 
			
		||||
            $this->temp_res = $res;
 | 
			
		||||
            return true;
 | 
			
		||||
        } else {
 | 
			
		||||
            common_debug('ActivityPub Explorer: Invalid potential remote actor while ensuring URI: '.$url. '. He returned the following: '.json_encode($res, JSON_UNESCAPED_SLASHES));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a local user profile from its URL and joins it on
 | 
			
		||||
         * Get a local user profiles from its URL and joins it on
 | 
			
		||||
         * $this->discovered_actor_profiles
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param string $uri Actor's uri
 | 
			
		||||
         * @param string $url User's url
 | 
			
		||||
         * @return boolean success state
 | 
			
		||||
         */
 | 
			
		||||
    private function grab_local_user($uri, $online = false)
 | 
			
		||||
        private function grab_local_user ($url)
 | 
			
		||||
        {
 | 
			
		||||
        if ($online) {
 | 
			
		||||
            common_debug('ActivityPub Explorer: Searching locally for '.$uri. ' with online resources.');
 | 
			
		||||
        } else {
 | 
			
		||||
            common_debug('ActivityPub Explorer: Searching locally for '.$uri. ' offline.');
 | 
			
		||||
        }
 | 
			
		||||
        // Ensure proper remote URI
 | 
			
		||||
        // If an exception occurs here it's better to just leave everything
 | 
			
		||||
        // break than to continue processing
 | 
			
		||||
        if ($online && $this->ensure_proper_remote_uri($uri)) {
 | 
			
		||||
            $uri = $this->temp_res["id"];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Try standard ActivityPub route
 | 
			
		||||
        // Is this a known filthy little mudblood?
 | 
			
		||||
        $aprofile = self::get_aprofile_by_url($uri);
 | 
			
		||||
        if ($aprofile instanceof Activitypub_profile) {
 | 
			
		||||
            $profile = $aprofile->local_profile();
 | 
			
		||||
            common_debug('ActivityPub Explorer: Found a local Aprofile for '.$uri);
 | 
			
		||||
            // We found something!
 | 
			
		||||
            $this->discovered_actor_profiles[]= $profile;
 | 
			
		||||
            unset($this->temp_res); // IMPORTANT to avoid _dangerous_ noise in the Explorer system
 | 
			
		||||
                if (($actor_profile = self::get_profile_by_url ($url)) != false) {
 | 
			
		||||
                        $this->discovered_actor_profiles[]= $actor_profile;
 | 
			
		||||
                        return true;
 | 
			
		||||
                } else {
 | 
			
		||||
            common_debug('ActivityPub Explorer: Unable to find a local Aprofile for '.$uri.' - looking for a Profile instead.');
 | 
			
		||||
            // Well, maybe it is a pure blood?
 | 
			
		||||
            // Iff, we are in the same instance:
 | 
			
		||||
            $ACTIVITYPUB_BASE_ACTOR_URI_length = strlen(ACTIVITYPUB_BASE_ACTOR_URI);
 | 
			
		||||
            if (substr($uri, 0, $ACTIVITYPUB_BASE_ACTOR_URI_length) == ACTIVITYPUB_BASE_ACTOR_URI) {
 | 
			
		||||
                try {
 | 
			
		||||
                    $profile = Profile::getByID(intval(substr($uri, $ACTIVITYPUB_BASE_ACTOR_URI_length)));
 | 
			
		||||
                    common_debug('ActivityPub Explorer: Found a Profile for '.$uri);
 | 
			
		||||
                    // We found something!
 | 
			
		||||
                    $this->discovered_actor_profiles[]= $profile;
 | 
			
		||||
                    unset($this->temp_res); // IMPORTANT to avoid _dangerous_ noise in the Explorer system
 | 
			
		||||
                        /******************************** XXX: ********************************
 | 
			
		||||
                         * Sometimes it is not true that the user is not locally available,   *
 | 
			
		||||
                         * mostly when it is a local user and URLs slightly changed           *
 | 
			
		||||
                         * e.g.: GS instance owner changed from standard urls to pretty urls  *
 | 
			
		||||
                         * (not sure if this is necessary, but anyway)                        *
 | 
			
		||||
                         **********************************************************************/
 | 
			
		||||
 | 
			
		||||
                        // Iff we really are in the same instance
 | 
			
		||||
                        $root_url_len = strlen (common_root_url ());
 | 
			
		||||
                        if (substr ($url, 0, $root_url_len) == common_root_url ()) {
 | 
			
		||||
                                // Grab the nickname and try to get the user
 | 
			
		||||
                                if (($actor_profile = Profile::getKV ("nickname", substr ($url, $root_url_len))) != false) {
 | 
			
		||||
                                        $this->discovered_actor_profiles[]= $actor_profile;
 | 
			
		||||
                                        return true;
 | 
			
		||||
                } catch (Exception $e) {
 | 
			
		||||
                    // Let the exception go on its merry way.
 | 
			
		||||
                    common_debug('ActivityPub Explorer: Unable to find a Profile for '.$uri);
 | 
			
		||||
                                }
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        // If offline grabbing failed, attempt again with online resources
 | 
			
		||||
        if (!$online) {
 | 
			
		||||
            common_debug('ActivityPub Explorer: Will try everything again with online resources against: '.$uri);
 | 
			
		||||
            return $this->grab_local_user($uri, true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -194,157 +116,70 @@ class Activitypub_explorer
 | 
			
		||||
         * Get a remote user(s) profile(s) from its URL and joins it on
 | 
			
		||||
         * $this->discovered_actor_profiles
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @param string $url User's url
 | 
			
		||||
         * @return boolean success state
 | 
			
		||||
         */
 | 
			
		||||
    private function grab_remote_user($url)
 | 
			
		||||
        private function grab_remote_user ($url)
 | 
			
		||||
        {
 | 
			
		||||
        common_debug('ActivityPub Explorer: Trying to grab a remote actor for '.$url);
 | 
			
		||||
        if (!isset($this->temp_res)) {
 | 
			
		||||
            $client    = new HTTPClient();
 | 
			
		||||
            $headers   = [];
 | 
			
		||||
                $client    = new HTTPClient ();
 | 
			
		||||
                $headers   = array();
 | 
			
		||||
                $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
 | 
			
		||||
                $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
 | 
			
		||||
            $response  = $client->get($url, $headers);
 | 
			
		||||
            $res = json_decode($response->getBody(), true);
 | 
			
		||||
        } else {
 | 
			
		||||
            $res = $this->temp_res;
 | 
			
		||||
            unset($this->temp_res);
 | 
			
		||||
                $response  = $client->get ($url, $headers);
 | 
			
		||||
                if (!$response->isOk ()) {
 | 
			
		||||
                    throw new Exception ("Invalid Actor URL.");
 | 
			
		||||
                }
 | 
			
		||||
                $res = json_decode ($response->getBody (), JSON_UNESCAPED_SLASHES);
 | 
			
		||||
                if (isset ($res["orderedItems"])) { // It's a potential collection of actors!!!
 | 
			
		||||
                        foreach ($res["orderedItems"] as $profile) {
 | 
			
		||||
                                if ($this->_lookup ($profile) == false) {
 | 
			
		||||
                                        // XXX: Invalid actor found, not sure how we handle those
 | 
			
		||||
                                }
 | 
			
		||||
                        }
 | 
			
		||||
                        // Go through entire collection
 | 
			
		||||
                        if (!is_null ($res["next"])) {
 | 
			
		||||
                                $this->_lookup ($res["next"]);
 | 
			
		||||
                        }
 | 
			
		||||
        if (isset($res['type']) && $res['type'] === 'OrderedCollection' && isset($res['first'])) { // It's a potential collection of actors!!!
 | 
			
		||||
            common_debug('ActivityPub Explorer: Found a collection of actors for '.$url);
 | 
			
		||||
            $this->travel_collection($res['first']);
 | 
			
		||||
                        return true;
 | 
			
		||||
        } elseif (self::validate_remote_response($res)) {
 | 
			
		||||
            common_debug('ActivityPub Explorer: Found a valid remote actor for '.$url);
 | 
			
		||||
            $this->discovered_actor_profiles[]= $this->store_profile($res);
 | 
			
		||||
                } else if ($this->validate_remote_response ($res)) {
 | 
			
		||||
                        $this->discovered_actor_profiles[]= $this->store_profile ($res);
 | 
			
		||||
                        return true;
 | 
			
		||||
        } else {
 | 
			
		||||
            common_debug('ActivityPub Explorer: Invalid potential remote actor while grabbing remotely: '.$url. '. He returned the following: '.json_encode($res, JSON_UNESCAPED_SLASHES));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
        // TODO: Fallback to OStatus
 | 
			
		||||
                return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Save remote user profile in local instance
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @param array $res remote response
 | 
			
		||||
         * @return Profile remote Profile object
 | 
			
		||||
         */
 | 
			
		||||
    private function store_profile($res)
 | 
			
		||||
        private function store_profile ($res)
 | 
			
		||||
        {
 | 
			
		||||
        // ActivityPub Profile
 | 
			
		||||
                $aprofile                 = new Activitypub_profile;
 | 
			
		||||
        $aprofile->uri            = $res['id'];
 | 
			
		||||
        $aprofile->nickname       = $res['preferredUsername'];
 | 
			
		||||
        $aprofile->fullname       = isset($res['name']) ? $res['name'] : null;
 | 
			
		||||
        $aprofile->bio            = isset($res['summary']) ? substr(strip_tags($res['summary']), 0, 1000) : null;
 | 
			
		||||
        $aprofile->inboxuri       = $res['inbox'];
 | 
			
		||||
        $aprofile->sharedInboxuri = isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : $res['inbox'];
 | 
			
		||||
                $aprofile->uri            = $res["url"];
 | 
			
		||||
                $aprofile->nickname       = $res["nickname"];
 | 
			
		||||
                $aprofile->fullname       = $res["display_name"];
 | 
			
		||||
                $aprofile->bio            = substr ($res["summary"], 0, 1000);
 | 
			
		||||
                $aprofile->inboxuri       = $res["inbox"];
 | 
			
		||||
                $aprofile->sharedInboxuri = $res["sharedInbox"];
 | 
			
		||||
 | 
			
		||||
        $aprofile->do_insert();
 | 
			
		||||
        $profile = $aprofile->local_profile();
 | 
			
		||||
                $aprofile->doInsert ();
 | 
			
		||||
 | 
			
		||||
        // Public Key
 | 
			
		||||
        $apRSA = new Activitypub_rsa();
 | 
			
		||||
        $apRSA->profile_id = $profile->getID();
 | 
			
		||||
        $apRSA->public_key = $res['publicKey']['publicKeyPem'];
 | 
			
		||||
        $apRSA->store_keys();
 | 
			
		||||
 | 
			
		||||
        // Avatar
 | 
			
		||||
        if (isset($res['icon']['url'])) {
 | 
			
		||||
            try {
 | 
			
		||||
                $this->update_avatar($profile, $res['icon']['url']);
 | 
			
		||||
            } catch (Exception $e) {
 | 
			
		||||
                // Let the exception go, it isn't a serious issue
 | 
			
		||||
                common_debug('ActivityPub Explorer: An error ocurred while grabbing remote avatar: '.$e->getMessage());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $profile;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download and update given avatar image
 | 
			
		||||
     *
 | 
			
		||||
     * @author GNU Social
 | 
			
		||||
     * @param  Profile $profile
 | 
			
		||||
     * @param  string $url
 | 
			
		||||
     * @return Avatar    The Avatar we have on disk.
 | 
			
		||||
     * @throws Exception in various failure cases
 | 
			
		||||
     */
 | 
			
		||||
    public static function update_avatar(Profile $profile, $url)
 | 
			
		||||
    {
 | 
			
		||||
        common_debug('ActivityPub Explorer: Started grabbing remote avatar from: '.$url);
 | 
			
		||||
        if (!filter_var($url, FILTER_VALIDATE_URL)) {
 | 
			
		||||
            // TRANS: Server exception. %s is a URL.
 | 
			
		||||
            common_debug('ActivityPub Explorer: Failed because it is an invalid url: '.$url);
 | 
			
		||||
            throw new ServerException(sprintf('Invalid avatar URL %s.'), $url);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // @todo FIXME: This should be better encapsulated
 | 
			
		||||
        // ripped from oauthstore.php (for old OMB client)
 | 
			
		||||
        $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
 | 
			
		||||
        try {
 | 
			
		||||
            $imgData = HTTPClient::quickGet($url);
 | 
			
		||||
            // Make sure it's at least an image file. ImageFile can do the rest.
 | 
			
		||||
            if (false === getimagesizefromstring($imgData)) {
 | 
			
		||||
                common_debug('ActivityPub Explorer: Failed because the downloaded avatar: '.$url. 'is not a valid image.');
 | 
			
		||||
                throw new UnsupportedMediaException('Downloaded avatar was not an image.');
 | 
			
		||||
            }
 | 
			
		||||
            file_put_contents($temp_filename, $imgData);
 | 
			
		||||
            unset($imgData);    // No need to carry this in memory.
 | 
			
		||||
            common_debug('ActivityPub Explorer: Stored dowloaded avatar in: '.$temp_filename);
 | 
			
		||||
 | 
			
		||||
            $id = $profile->getID();
 | 
			
		||||
 | 
			
		||||
            $imagefile = new ImageFile(null, $temp_filename);
 | 
			
		||||
            $filename = Avatar::filename(
 | 
			
		||||
                $id,
 | 
			
		||||
                image_type_to_extension($imagefile->type),
 | 
			
		||||
                null,
 | 
			
		||||
                common_timestamp()
 | 
			
		||||
            );
 | 
			
		||||
            rename($temp_filename, Avatar::path($filename));
 | 
			
		||||
            common_debug('ActivityPub Explorer: Moved avatar from: '.$temp_filename.' to '.$filename);
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            common_debug('ActivityPub Explorer: Something went wrong while processing the avatar from: '.$url.' details: '.$e->getMessage());
 | 
			
		||||
            unlink($temp_filename);
 | 
			
		||||
            throw $e;
 | 
			
		||||
        }
 | 
			
		||||
        // @todo FIXME: Hardcoded chmod is lame, but seems to be necessary to
 | 
			
		||||
        // keep from accidentally saving images from command-line (queues)
 | 
			
		||||
        // that can't be read from web server, which causes hard-to-notice
 | 
			
		||||
        // problems later on:
 | 
			
		||||
        //
 | 
			
		||||
        // http://status.net/open-source/issues/2663
 | 
			
		||||
        chmod(Avatar::path($filename), 0644);
 | 
			
		||||
 | 
			
		||||
        $profile->setOriginal($filename);
 | 
			
		||||
 | 
			
		||||
        $orig = clone($profile);
 | 
			
		||||
        $profile->avatar = $url;
 | 
			
		||||
        $profile->update($orig);
 | 
			
		||||
 | 
			
		||||
        common_debug('ActivityPub Explorer: Seted Avatar from: '.$url.' to profile.');
 | 
			
		||||
        return Avatar::getUploaded($profile);
 | 
			
		||||
                return $aprofile->localProfile ();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Validates a remote response in order to determine whether this
 | 
			
		||||
         * response is a valid profile or not
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @param array $res remote response
 | 
			
		||||
         * @return boolean success state
 | 
			
		||||
         */
 | 
			
		||||
    public static function validate_remote_response($res)
 | 
			
		||||
        private function validate_remote_response ($res)
 | 
			
		||||
        {
 | 
			
		||||
        if (!isset($res['id'], $res['preferredUsername'], $res['inbox'], $res['publicKey']['publicKeyPem'])) {
 | 
			
		||||
                if (!isset ($res["url"], $res["nickname"], $res["display_name"], $res["summary"], $res["inbox"], $res["sharedInbox"])) {
 | 
			
		||||
                        return false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -352,21 +187,20 @@ class Activitypub_explorer
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
     * Get a ActivityPub Profile from it's uri
 | 
			
		||||
         * Get a profile from it's profileurl
 | 
			
		||||
         * Unfortunately GNU Social cache is not truly reliable when handling
 | 
			
		||||
         * potential ActivityPub remote profiles, as so it is important to use
 | 
			
		||||
         * this hacky workaround (at least for now)
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         * @param string $v URL
 | 
			
		||||
     * @return boolean|Activitypub_profile false if fails | Aprofile object if successful
 | 
			
		||||
         * @return boolean|Profile false if fails | Profile object if successful
 | 
			
		||||
         */
 | 
			
		||||
    public static function get_aprofile_by_url($v)
 | 
			
		||||
        static function get_profile_by_url ($v)
 | 
			
		||||
        {
 | 
			
		||||
        $i = Managed_DataObject::getcached("Activitypub_profile", "uri", $v);
 | 
			
		||||
        if (empty($i)) { // false = cache miss
 | 
			
		||||
            $i = new Activitypub_profile;
 | 
			
		||||
            $result = $i->get("uri", $v);
 | 
			
		||||
                $i = Managed_DataObject::getcached(Profile, "profileurl", $v);
 | 
			
		||||
                if (empty ($i)) { // false = cache miss
 | 
			
		||||
                        $i = new Profile;
 | 
			
		||||
                        $result = $i->get ("profileurl", $v);
 | 
			
		||||
                        if ($result) {
 | 
			
		||||
                                // Hit!
 | 
			
		||||
                                $i->encache();
 | 
			
		||||
@@ -376,89 +210,4 @@ class Activitypub_explorer
 | 
			
		||||
                }
 | 
			
		||||
                return $i;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a valid actor profile url returns its inboxes
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param string $url of Actor profile
 | 
			
		||||
     * @return boolean|array false if fails | array with inbox and shared inbox if successful
 | 
			
		||||
     */
 | 
			
		||||
    public static function get_actor_inboxes_uri($url)
 | 
			
		||||
    {
 | 
			
		||||
        $client    = new HTTPClient();
 | 
			
		||||
        $headers   = [];
 | 
			
		||||
        $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
 | 
			
		||||
        $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
 | 
			
		||||
        $response  = $client->get($url, $headers);
 | 
			
		||||
        if (!$response->isOk()) {
 | 
			
		||||
            throw new Exception('Invalid Actor URL.');
 | 
			
		||||
        }
 | 
			
		||||
        $res = json_decode($response->getBody(), true);
 | 
			
		||||
        if (self::validate_remote_response($res)) {
 | 
			
		||||
            return [
 | 
			
		||||
                'inbox' => $res['inbox'],
 | 
			
		||||
                'sharedInbox' => isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : $res['inbox']
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Allows the Explorer to transverse a collection of persons.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param type $url
 | 
			
		||||
     * @return boolean
 | 
			
		||||
     */
 | 
			
		||||
    private function travel_collection($url)
 | 
			
		||||
    {
 | 
			
		||||
        $client    = new HTTPClient();
 | 
			
		||||
        $headers   = [];
 | 
			
		||||
        $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
 | 
			
		||||
        $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
 | 
			
		||||
        $response  = $client->get($url, $headers);
 | 
			
		||||
        $res = json_decode($response->getBody(), true);
 | 
			
		||||
 | 
			
		||||
        if (!isset($res['orderedItems'])) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($res["orderedItems"] as $profile) {
 | 
			
		||||
            if ($this->_lookup($profile) == false) {
 | 
			
		||||
                common_debug('ActivityPub Explorer: Found an invalid actor for '.$profile);
 | 
			
		||||
                // TODO: Invalid actor found, fallback to OStatus
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Go through entire collection
 | 
			
		||||
        if (!is_null($res["next"])) {
 | 
			
		||||
            $this->_lookup($res["next"]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a remote user array from its URL (this function is only used for
 | 
			
		||||
     * profile updating and shall not be used for anything else)
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param string $url User's url
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public static function get_remote_user_activity($url)
 | 
			
		||||
    {
 | 
			
		||||
        $client    = new HTTPClient();
 | 
			
		||||
        $headers   = [];
 | 
			
		||||
        $headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
 | 
			
		||||
        $headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
 | 
			
		||||
        $response  = $client->get($url, $headers);
 | 
			
		||||
        $res = json_decode($response->getBody(), true);
 | 
			
		||||
        if (Activitypub_explorer::validate_remote_response($res)) {
 | 
			
		||||
            common_debug('ActivityPub Explorer: Found a valid remote actor for '.$url);
 | 
			
		||||
            return $res;
 | 
			
		||||
        }
 | 
			
		||||
        throw new Exception('ActivityPub Explorer: Failed to get activity.');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,300 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * ActivityPubPlugin implementation for GNU Social
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub Inbox Handler
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
class Activitypub_inbox_handler
 | 
			
		||||
{
 | 
			
		||||
    private $activity;
 | 
			
		||||
    private $actor;
 | 
			
		||||
    private $object;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a Inbox Handler to receive something from someone.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Array $activity Activity we are receiving
 | 
			
		||||
     * @param Profile $actor_profile Actor originating the activity
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct($activity, $actor_profile = null)
 | 
			
		||||
    {
 | 
			
		||||
        $this->activity = $activity;
 | 
			
		||||
        $this->object = $activity['object'];
 | 
			
		||||
 | 
			
		||||
        // Validate Activity
 | 
			
		||||
        $this->validate_activity();
 | 
			
		||||
 | 
			
		||||
        // Get Actor's Profile
 | 
			
		||||
        if (!is_null($actor_profile)) {
 | 
			
		||||
            $this->actor = $actor_profile;
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->actor = ActivityPub_explorer::get_profile_from_url($this->activity['actor']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Handle the Activity
 | 
			
		||||
        $this->process();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validates if a given Activity is valid. Throws exception if not.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.upt.pt>
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    private function validate_activity()
 | 
			
		||||
    {
 | 
			
		||||
        // Activity validation
 | 
			
		||||
        // Validate data
 | 
			
		||||
        if (!(isset($this->activity['type']))) {
 | 
			
		||||
            throw new Exception('Activity Validation Failed: Type was not specified.');
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($this->activity['actor'])) {
 | 
			
		||||
            throw new Exception('Activity Validation Failed: Actor was not specified.');
 | 
			
		||||
        }
 | 
			
		||||
        if (!isset($this->activity['object'])) {
 | 
			
		||||
            throw new Exception('Activity Validation Failed: Object was not specified.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Object validation
 | 
			
		||||
        switch ($this->activity['type']) {
 | 
			
		||||
            case 'Accept':
 | 
			
		||||
                Activitypub_accept::validate_object($this->object);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'Create':
 | 
			
		||||
                Activitypub_create::validate_object($this->object);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'Delete':
 | 
			
		||||
            case 'Follow':
 | 
			
		||||
            case 'Like':
 | 
			
		||||
            case 'Announce':
 | 
			
		||||
                if (!filter_var($this->object, FILTER_VALIDATE_URL)) {
 | 
			
		||||
                    throw new Exception('Object is not a valid Object URI for Activity.');
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case 'Undo':
 | 
			
		||||
                Activitypub_undo::validate_object($this->object);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new Exception('Unknown Activity Type.');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends the Activity to proper handler in order to be processed.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.upt.pt>
 | 
			
		||||
     */
 | 
			
		||||
    private function process()
 | 
			
		||||
    {
 | 
			
		||||
        switch ($this->activity['type']) {
 | 
			
		||||
            case 'Accept':
 | 
			
		||||
                $this->handle_accept($this->actor, $this->object);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'Create':
 | 
			
		||||
                $this->handle_create($this->actor, $this->object);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'Delete':
 | 
			
		||||
                $this->handle_delete($this->actor, $this->object);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'Follow':
 | 
			
		||||
                $this->handle_follow($this->actor, $this->object);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'Like':
 | 
			
		||||
                $this->handle_like($this->actor, $this->object);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'Undo':
 | 
			
		||||
                $this->handle_undo($this->actor, $this->object);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'Announce':
 | 
			
		||||
                $this->handle_announce($this->actor, $this->object);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles an Accept Activity received by our inbox.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.upt.pt>
 | 
			
		||||
     * @param Profile $actor Actor
 | 
			
		||||
     * @param Array $object Activity
 | 
			
		||||
     */
 | 
			
		||||
    private function handle_accept($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        switch ($object['type']) {
 | 
			
		||||
            case 'Follow':
 | 
			
		||||
                $this->handle_accept_follow($actor, $object);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles an Accept Follow Activity received by our inbox.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.upt.pt>
 | 
			
		||||
     * @param Profile $actor Actor
 | 
			
		||||
     * @param Array $object Activity
 | 
			
		||||
     */
 | 
			
		||||
    private function handle_accept_follow($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        // Get valid Object profile
 | 
			
		||||
        $object_profile = new Activitypub_explorer;
 | 
			
		||||
        $object_profile = $object_profile->lookup($object['object'])[0];
 | 
			
		||||
 | 
			
		||||
        $pending_list = new Activitypub_pending_follow_requests($actor->getID(), $object_profile->getID());
 | 
			
		||||
        $pending_list->remove();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a Create Activity received by our inbox.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.upt.pt>
 | 
			
		||||
     * @param Profile $actor Actor
 | 
			
		||||
     * @param Array $object Activity
 | 
			
		||||
     */
 | 
			
		||||
    private function handle_create($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        switch ($object['type']) {
 | 
			
		||||
            case 'Note':
 | 
			
		||||
                Activitypub_notice::create_notice($object, $actor);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a Delete Activity received by our inbox.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.upt.pt>
 | 
			
		||||
     * @param Profile $actor Actor
 | 
			
		||||
     * @param Array $object Activity
 | 
			
		||||
     */
 | 
			
		||||
    private function handle_delete($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        $notice = ActivityPubPlugin::grab_notice_from_url($object['object']);
 | 
			
		||||
        $notice->deleteAs($actor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a Follow Activity received by our inbox.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.upt.pt>
 | 
			
		||||
     * @param Profile $actor Actor
 | 
			
		||||
     * @param Array $object Activity
 | 
			
		||||
     */
 | 
			
		||||
    private function handle_follow($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        Activitypub_follow::follow($actor, $object);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a Like Activity received by our inbox.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.upt.pt>
 | 
			
		||||
     * @param Profile $actor Actor
 | 
			
		||||
     * @param Array $object Activity
 | 
			
		||||
     */
 | 
			
		||||
    private function handle_like($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        $notice = ActivityPubPlugin::grab_notice_from_url($object);
 | 
			
		||||
        Fave::addNew($actor, $notice);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a Undo Activity received by our inbox.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.upt.pt>
 | 
			
		||||
     * @param Profile $actor Actor
 | 
			
		||||
     * @param Array $object Activity
 | 
			
		||||
     */
 | 
			
		||||
    private function handle_undo($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        switch ($object['type']) {
 | 
			
		||||
            case 'Follow':
 | 
			
		||||
                $this->handle_undo_follow($actor, $object['object']);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'Like':
 | 
			
		||||
                $this->handle_undo_like($actor, $object['object']);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a Undo Like Activity received by our inbox.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.upt.pt>
 | 
			
		||||
     * @param Profile $actor Actor
 | 
			
		||||
     * @param Array $object Activity
 | 
			
		||||
     */
 | 
			
		||||
    private function handle_undo_like($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        $notice = ActivityPubPlugin::grab_notice_from_url($object);
 | 
			
		||||
        Fave::removeEntry($actor, $notice);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a Undo Follow Activity received by our inbox.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.upt.pt>
 | 
			
		||||
     * @param Profile $actor Actor
 | 
			
		||||
     * @param Array $object Activity
 | 
			
		||||
     */
 | 
			
		||||
    private function handle_undo_follow($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        // Get Object profile
 | 
			
		||||
        $object_profile = new Activitypub_explorer;
 | 
			
		||||
        $object_profile = $object_profile->lookup($object)[0];
 | 
			
		||||
 | 
			
		||||
        if (Subscription::exists($actor, $object_profile)) {
 | 
			
		||||
            Subscription::cancel($actor, $object_profile);
 | 
			
		||||
        // You are no longer following this person.
 | 
			
		||||
        } else {
 | 
			
		||||
            // 409: You are not following this person already.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a Announce Activity received by our inbox.
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.upt.pt>
 | 
			
		||||
     * @param Profile $actor Actor
 | 
			
		||||
     * @param Array $object Activity
 | 
			
		||||
     */
 | 
			
		||||
    private function handle_announce($actor, $object)
 | 
			
		||||
    {
 | 
			
		||||
        $object_notice = ActivityPubPlugin::grab_notice_from_url($object);
 | 
			
		||||
        $object_notice->repeat($actor, 'ActivityPub');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										280
									
								
								utils/postman.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										280
									
								
								utils/postman.php
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -20,18 +20,15 @@
 | 
			
		||||
 * @category  Plugin
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
 * @author    Daniel Supernault <danielsupernault@gmail.com>
 | 
			
		||||
 * @copyright 2018 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
if (!defined ('GNUSOCIAL')) {
 | 
			
		||||
        exit (1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use GuzzleHttp\Client;
 | 
			
		||||
use HttpSignatures\Context;
 | 
			
		||||
use HttpSignatures\GuzzleHttpSignatures;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ActivityPub's own Postman
 | 
			
		||||
 *
 | 
			
		||||
@@ -47,276 +44,53 @@ use HttpSignatures\GuzzleHttpSignatures;
 | 
			
		||||
class Activitypub_postman
 | 
			
		||||
{
 | 
			
		||||
        private $actor;
 | 
			
		||||
    private $actor_uri;
 | 
			
		||||
    private $to = [];
 | 
			
		||||
        private $to = array ();
 | 
			
		||||
        private $client;
 | 
			
		||||
        private $headers;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Create a postman to deliver something to someone
 | 
			
		||||
         *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Profile $from Profile of sender
 | 
			
		||||
     * @param Array of Activitypub_profile $to destinataries
 | 
			
		||||
         * @param Activitypub_profile $to array of destinataries
 | 
			
		||||
         */
 | 
			
		||||
    public function __construct($from, $to)
 | 
			
		||||
        public function __construct ($from, $to)
 | 
			
		||||
        {
 | 
			
		||||
                $this->actor = $from;
 | 
			
		||||
        $discovery = new Activitypub_explorer();
 | 
			
		||||
                $this->to = $to;
 | 
			
		||||
        $followers = apActorFollowersAction::generate_followers($this->actor, 0, null);
 | 
			
		||||
        foreach ($followers as $sub) {
 | 
			
		||||
            try {
 | 
			
		||||
                $to[]= Activitypub_profile::from_profile($discovery->lookup($sub)[0]);
 | 
			
		||||
            } catch (Exception $e) {
 | 
			
		||||
                // Not an ActivityPub Remote Follower, let it go
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        unset($discovery);
 | 
			
		||||
 | 
			
		||||
        $this->actor_uri = ActivityPubPlugin::actor_uri($this->actor);
 | 
			
		||||
 | 
			
		||||
        $actor_private_key = new Activitypub_rsa();
 | 
			
		||||
        $actor_private_key = $actor_private_key->get_private_key($this->actor);
 | 
			
		||||
 | 
			
		||||
        $context = new Context([
 | 
			
		||||
            'keys' => [$this->actor_uri.'#public-key' => $actor_private_key],
 | 
			
		||||
            'algorithm' => 'rsa-sha256',
 | 
			
		||||
            'headers' => ['(request-target)', 'date', 'content-type', 'accept', 'user-agent'],
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $this->headers = [
 | 
			
		||||
            'content-type' => 'application/activity+json',
 | 
			
		||||
            'accept'       => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
 | 
			
		||||
            'user-agent'   => 'GNUSocialBot v0.1 - https://gnu.io/social',
 | 
			
		||||
            'date'         => gmdate('D, d M Y H:i:s \G\M\T', time())
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        $handlerStack = GuzzleHttpSignatures::defaultHandlerFromContext($context);
 | 
			
		||||
        $this->client = new Client(['handler' => $handlerStack]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send something to remote instance
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param string $data request body
 | 
			
		||||
     * @param string $inbox url of remote inbox
 | 
			
		||||
     * @param string $method request method
 | 
			
		||||
     * @return Psr\Http\Message\ResponseInterface
 | 
			
		||||
     */
 | 
			
		||||
    public function send($data, $inbox, $method = 'POST')
 | 
			
		||||
    {
 | 
			
		||||
        common_debug('ActivityPub Postman: Delivering '.$data.' to '.$inbox);
 | 
			
		||||
        $response = $this->client->request($method, $inbox, ['headers' => array_merge($this->headers, ['(request-target)' => strtolower($method).' '.parse_url($inbox, PHP_URL_PATH)]),'body' => $data]);
 | 
			
		||||
        common_debug('ActivityPub Postman: Delivery result with status code '.$response->getStatusCode().': '.$response->getBody()->getContents());
 | 
			
		||||
        return $response;
 | 
			
		||||
                $this->headers = array();
 | 
			
		||||
                $this->headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
 | 
			
		||||
                $this->headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Send a follow notification to remote instance
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
         */
 | 
			
		||||
    public function follow()
 | 
			
		||||
        public function follow ()
 | 
			
		||||
        {
 | 
			
		||||
        $data = Activitypub_follow::follow_to_array(ActivityPubPlugin::actor_uri($this->actor), $this->to[0]->getUrl());
 | 
			
		||||
        $res = $this->send(json_encode($data, JSON_UNESCAPED_SLASHES), $this->to[0]->get_inbox());
 | 
			
		||||
        $res_body = json_decode($res->getBody()->getContents());
 | 
			
		||||
 | 
			
		||||
        if ($res->getStatusCode() == 200 || $res->getStatusCode() == 202 || $res->getStatusCode() == 409) {
 | 
			
		||||
            $pending_list = new Activitypub_pending_follow_requests($this->actor->getID(), $this->to[0]->getID());
 | 
			
		||||
            $pending_list->add();
 | 
			
		||||
            return true;
 | 
			
		||||
        } elseif (isset($res_body[0]->error)) {
 | 
			
		||||
            throw new Exception($res_body[0]->error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new Exception("An unknown error occurred.");
 | 
			
		||||
                $this->client = new HTTPClient ();
 | 
			
		||||
                $data = array ("@context" => "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                          "type"   => "Follow",
 | 
			
		||||
                          "actor"  => $this->actor->getUrl (),
 | 
			
		||||
                          "object" => $this->to[0]->getUrl ());
 | 
			
		||||
                $this->client->setBody (json_encode ($data));
 | 
			
		||||
                $response = $this->client->post ($this->to[0]->getInbox (), $this->headers);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Send a Undo Follow notification to remote instance
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
         */
 | 
			
		||||
    public function undo_follow()
 | 
			
		||||
        public function undo_follow ()
 | 
			
		||||
        {
 | 
			
		||||
        $data = Activitypub_undo::undo_to_array(
 | 
			
		||||
                    Activitypub_follow::follow_to_array(
 | 
			
		||||
                        ActivityPubPlugin::actor_uri($this->actor),
 | 
			
		||||
                        $this->to[0]->getUrl()
 | 
			
		||||
                $this->client = new HTTPClient ();
 | 
			
		||||
                $data = array ("@context" => "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                            "type"   => "Undo",
 | 
			
		||||
                            "actor"  => $this->actor->getUrl (),
 | 
			
		||||
                            "object" => array (
 | 
			
		||||
                                "type" => "Follow",
 | 
			
		||||
                                "object" => $this->to[0]->getUrl ()
 | 
			
		||||
                            )
 | 
			
		||||
                        );
 | 
			
		||||
        $res = $this->send(json_encode($data, JSON_UNESCAPED_SLASHES), $this->to[0]->get_inbox());
 | 
			
		||||
        $res_body = json_decode($res->getBody()->getContents());
 | 
			
		||||
 | 
			
		||||
        if ($res->getStatusCode() == 200 || $res->getStatusCode() == 202 || $res->getStatusCode() == 409) {
 | 
			
		||||
            $pending_list = new Activitypub_pending_follow_requests($this->actor->getID(), $this->to[0]->getID());
 | 
			
		||||
            $pending_list->remove();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($res_body[0]->error)) {
 | 
			
		||||
            throw new Exception($res_body[0]->error);
 | 
			
		||||
        }
 | 
			
		||||
        throw new Exception("An unknown error occurred.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send a Accept Follow notification to remote instance
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     */
 | 
			
		||||
    public function accept_follow()
 | 
			
		||||
    {
 | 
			
		||||
        $data = Activitypub_accept::accept_to_array(
 | 
			
		||||
                    Activitypub_follow::follow_to_array(
 | 
			
		||||
                       $this->to[0]->getUrl(),
 | 
			
		||||
                       ActivityPubPlugin::actor_uri($this->actor)
 | 
			
		||||
 | 
			
		||||
                    )
 | 
			
		||||
               );
 | 
			
		||||
        $res = $this->send(json_encode($data, JSON_UNESCAPED_SLASHES), $this->to[0]->get_inbox());
 | 
			
		||||
        $res_body = json_decode($res->getBody()->getContents());
 | 
			
		||||
 | 
			
		||||
        if ($res->getStatusCode() == 200 || $res->getStatusCode() == 202 || $res->getStatusCode() == 409) {
 | 
			
		||||
            $pending_list = new Activitypub_pending_follow_requests($this->actor->getID(), $this->to[0]->getID());
 | 
			
		||||
            $pending_list->remove();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($res_body[0]->error)) {
 | 
			
		||||
            throw new Exception($res_body[0]->error);
 | 
			
		||||
        }
 | 
			
		||||
        throw new Exception("An unknown error occurred.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send a Like notification to remote instances holding the notice
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Notice $notice
 | 
			
		||||
     */
 | 
			
		||||
    public function like($notice)
 | 
			
		||||
    {
 | 
			
		||||
        $data = Activitypub_like::like_to_array(
 | 
			
		||||
                    ActivityPubPlugin::actor_uri($this->actor),
 | 
			
		||||
                    $notice->getUrl()
 | 
			
		||||
                );
 | 
			
		||||
        $data = json_encode($data, JSON_UNESCAPED_SLASHES);
 | 
			
		||||
 | 
			
		||||
        foreach ($this->to_inbox() as $inbox) {
 | 
			
		||||
            $this->send($data, $inbox);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send a Undo Like notification to remote instances holding the notice
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Notice $notice
 | 
			
		||||
     */
 | 
			
		||||
    public function undo_like($notice)
 | 
			
		||||
    {
 | 
			
		||||
        $data = Activitypub_undo::undo_to_array(
 | 
			
		||||
                         Activitypub_like::like_to_array(
 | 
			
		||||
                             ActivityPubPlugin::actor_uri($this->actor),
 | 
			
		||||
                            $notice->getUrl()
 | 
			
		||||
                         )
 | 
			
		||||
                );
 | 
			
		||||
        $data = json_encode($data, JSON_UNESCAPED_SLASHES);
 | 
			
		||||
 | 
			
		||||
        foreach ($this->to_inbox() as $inbox) {
 | 
			
		||||
            $this->send($data, $inbox);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send a Create notification to remote instances
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Notice $notice
 | 
			
		||||
     */
 | 
			
		||||
    public function create_note($notice)
 | 
			
		||||
    {
 | 
			
		||||
        $data = Activitypub_create::create_to_array(
 | 
			
		||||
                    $this->actor_uri,
 | 
			
		||||
                    Activitypub_notice::notice_to_array($notice)
 | 
			
		||||
                );
 | 
			
		||||
        $data = json_encode($data, JSON_UNESCAPED_SLASHES);
 | 
			
		||||
 | 
			
		||||
        foreach ($this->to_inbox() as $inbox) {
 | 
			
		||||
            $this->send($data, $inbox);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send a Announce notification to remote instances
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Notice $notice
 | 
			
		||||
     */
 | 
			
		||||
    public function announce($notice)
 | 
			
		||||
    {
 | 
			
		||||
        $data = Activitypub_announce::announce_to_array(
 | 
			
		||||
                         ActivityPubPlugin::actor_uri($this->actor),
 | 
			
		||||
                         $notice->getUri()
 | 
			
		||||
                        );
 | 
			
		||||
        $data = json_encode($data, JSON_UNESCAPED_SLASHES);
 | 
			
		||||
 | 
			
		||||
        foreach ($this->to_inbox() as $inbox) {
 | 
			
		||||
            $this->send($data, $inbox);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send a Delete notification to remote instances holding the notice
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @param Notice $notice
 | 
			
		||||
     */
 | 
			
		||||
    public function delete($notice)
 | 
			
		||||
    {
 | 
			
		||||
        $data = Activitypub_delete::delete_to_array(
 | 
			
		||||
                    ActivityPubPlugin::actor_uri($notice->getProfile()),
 | 
			
		||||
                    $notice->getUrl()
 | 
			
		||||
                );
 | 
			
		||||
        $errors = [];
 | 
			
		||||
        $data = json_encode($data, JSON_UNESCAPED_SLASHES);
 | 
			
		||||
        foreach ($this->to_inbox() as $inbox) {
 | 
			
		||||
            $res = $this->send($data, $inbox);
 | 
			
		||||
            if (!$res->getStatusCode() == 200) {
 | 
			
		||||
                $res_body = json_decode($res->getBody()->getContents(), true);
 | 
			
		||||
                if (isset($res_body[0]['error'])) {
 | 
			
		||||
                    $errors[] = ($res_body[0]['error']);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                $errors[] = ("An unknown error occurred.");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!empty($errors)) {
 | 
			
		||||
            throw new Exception(json_encode($errors));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clean list of inboxes to deliver messages
 | 
			
		||||
     *
 | 
			
		||||
     * @author Diogo Cordeiro <diogo@fc.up.pt>
 | 
			
		||||
     * @return array To Inbox URLs
 | 
			
		||||
     */
 | 
			
		||||
    private function to_inbox()
 | 
			
		||||
    {
 | 
			
		||||
        $to_inboxes = [];
 | 
			
		||||
        foreach ($this->to as $to_profile) {
 | 
			
		||||
            $i = $to_profile->get_inbox();
 | 
			
		||||
            // Prevent delivering to self
 | 
			
		||||
            if ($i == [common_local_url('apInbox')]) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $to_inboxes[] = $i;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return array_unique($to_inboxes);
 | 
			
		||||
                $this->client->setBody (json_encode ($data));
 | 
			
		||||
                $response = $this->client->post ($this->to[0]->getInbox (), $this->headers);
 | 
			
		||||
        }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user