Initial commit: Complete workspace configuration

- MOper/ configurations
- home-assistant/ configurations
- scripts/ automation scripts
- unix/ system configurations
- docker/ Docker services (app, devtools, database, infra, maintenance, portainer, supervision, test)

Excludes: databases, logs, large files, Git submodules, secrets (via .gitignore)
This commit is contained in:
2026-01-02 16:09:32 +00:00
commit 07377e5237
6048 changed files with 1248288 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
{
"projectName": "Organizr",
"projectOwner": "causefx",
"files": [
"README.md"
],
"commitType": "docs",
"commitConvention": "angular",
"contributorsPerLine": 7,
"contributors": [
{
"login": "tronyx",
"name": "Chris Yocum",
"avatar_url": "https://avatars.githubusercontent.com/u/22502007?v=4",
"profile": "https://tronflix.app",
"contributions": [
"test"
]
},
{
"login": "Roxedus",
"name": "Roxedus",
"avatar_url": "https://avatars.githubusercontent.com/u/7110194?v=4",
"profile": "http://roxedus.dev",
"contributions": [
"test"
]
},
{
"login": "HalianElf",
"name": "HalianElf",
"avatar_url": "https://avatars.githubusercontent.com/u/28244771?v=4",
"profile": "https://github.com/HalianElf",
"contributions": [
"test"
]
}
]
}

View File

@@ -0,0 +1,19 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.css linguist-language=PHP
bower_components/* linguist-vendored

View File

@@ -0,0 +1,9 @@
# These are supported funding model platforms
github: causefx
patreon: organizr
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
custom: # Replace with a single custom sponsorship URL

View File

@@ -0,0 +1,22 @@
<!-- Please Fill out as much information as possible, Thanks! -->
###### Organizr Version: V 1.x
###### Branch: Master/Develop
###### WebServer: Nginx/Apache
###### Operating System: Windows/MacOS/Ubuntu
<hr>
##### Problem Description:
<!---TYPE HERE--->
<hr>
##### Reproduction Steps:
<!---TYPE HERE--->
<hr>
#### Errors on screen? If so paste here:
<!-- (Errors go below the first ``` . Don't remove the ' tags) -->
```
```

View File

@@ -0,0 +1,14 @@
name: Lock closed stale issue
on:
issues:
types: [closed]
jobs:
lock:
if: github.event.label.name == 'closed-no-issue-activity'
runs-on: ubuntu-latest
steps:
- uses: OSDKDev/lock-issues@v1.1
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -0,0 +1,21 @@
name: Close stale issues
on:
schedule:
- cron: "30 3 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs."
close-issue-message: "This issue has been closed due to lack of activity, if this issue still persists, please re-open it."
close-issue-label: "closed-no-issue-activity"
stale-issue-label: "no-issue-activity"
days-before-stale: 15
days-before-close: 90
exempt-issue-labels: "bypass-activity"
exempt-pr-labels: "bypass-activity"

View File

@@ -0,0 +1,194 @@
# PhpStorm Files
.idea/*
node_modules/*
package-lock.json
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Files from my local computer
php_errors.log
# Backup Files
.pydio_id
# =========================
# Organizr files
# =========================
organizr-*.log
databaseLocation.ini.php
homepageSettings.ini.php
loginLog.json
custom.css
_config.yml
org.log
org*.log
test.php
users.db
speedtest.db
chatpack.db
api.json
Cron.txt
Docker.txt
Github.txt
Demo.txt
DemoTest.txt
Dev.txt
updateInProgress.txt
config/cacert.pem
config/custom.pem
config/config.php
config/config*.bak.php
config/users/
config/users
config/users*.db
config/users*.bak.db
config/tmp/*
data/*
data/config/*
docs/api.json
images/cache/*
backups/*
backups/
backups
tracy/
logs/
debug.php
OrganizrV2/*
orgv2/*
test/*
organizrLoginLog.json
organizrLog.json
api/config/config*
upgrade/*
api/upgrade*
api/functions/cert/cacert.pem
api/functions/cert/custom.pem
wallpaper.jpg
strings.json
css/themes/*.css
!css/themes/Organizr.css
!css/themes/Blue.css
*sonflix*
plugins/images/cache/*.jpg
testdata*
strings.txt
strings.php
plugins/theme_files/*
plugins/plugin_files/*
!plugins/theme_files/index.html
!plugins/plugin_files/index.html
plugins/images/userTabs/*
!plugins/images/userTabs/index.html
plugins/images/faviconCustom/*
!plugins/images/faviconCustom/placeFavIconsHere.txt
api/v2/routes/custom/*
!api/v2/routes/custom/index.html
# =========================
# Plugin files
# =========================
api/plugins/*
!api/plugins/invites.php
!api/plugins/api/invites.php
!api/plugins/config/invites.php
!api/plugins/js/invites.js
!api/plugins/chat.php
!api/plugins/api/chat.php
!api/plugins/config/chat.php
!api/plugins/js/chat.js
!api/plugins/healthChecks.php
!api/plugins/api/healthChecks.php
!api/plugins/config/healthChecks.php
!api/plugins/js/healthChecks.js
!api/plugins/js/healthChecks-settings.js
!api/plugins/php-mailer.php
!api/plugins/api/php-mailer.php
!api/plugins/config/php-mailer.php
!api/plugins/js/php-mailer.js
!api/plugins/speedTest.php
!api/plugins/api/speedTest.php
!api/plugins/config/speedTest.php
!api/plugins/js/speedTest.js
!api/plugins/misc/emailTemplates/default.php
!api/plugins/misc/emailTemplates/gray.php
!api/plugins/misc/emailTemplates/light.php
!api/plugins/misc/emailTemplates/dark.php
!api/plugins/misc/emailTemplates/plehex.php
!api/plugins/misc/speedTest/empty.php
!api/plugins/misc/speedTest/garbage.php
!api/plugins/misc/speedTest/getIP.php
!api/plugins/misc/speedTest/telemetry.php
!api/plugins/misc/speedTest/telemetry_settings.php
!api/plugins/misc/speedTest/speedtest_worker.min.js
!api/plugins/bookmark.php
!api/plugins/api/bookmark.php
!api/plugins/config/bookmark.php
!api/plugins/js/bookmark-settings.js
!api/plugins/css/bookmark.css
!api/plugins/bookmark/
!api/plugins/chat/
!api/plugins/healthChecks/
!api/plugins/invites/
!api/plugins/php-mailer/
!api/plugins/speedTest/
!api/plugins/shuck-stop/
api/plugins/shuck-stop/drives.json
# =========================
# Custom files
# =========================
api/pages/custom/*.php
/plugins/images/tabs/eatsleep.jpg
/plugins/images/cache/tautulli-show.svg
/plugins/images/cache/tautulli-android.svg
/plugins/images/cache/tautulli-artist.svg
/plugins/images/cache/tautulli-movie.svg
/plugins/images/cache/tautulli-windows.svg
/plugins/images/cache/tautulli-samsung.svg
/plugins/images/cache/tautulli-chrome.svg
.vscode

View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders 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, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -0,0 +1,32 @@
# How to Contribute #
We're always looking for people to help make Organizr better.
## Documentation ##
[Docs](https://docs.organizr.app)
## Development ##
### Tools required ###
- Any IDE of your choice that supports PHP, HTML and Javascript.
- [Git](https://git-scm.com/downloads)
### Getting started ###
1. Fork Organizr
2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github)
3. Get to work
### Contributing Code ###
- If you're adding a new, already requested feature, please make a new Github issue so work is not duplicated (If you want to add something not already on there, please talk to us first)
- Rebase from Organizr's `develop` branch, don't merge
- Make meaningful commits, or squash them
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
- One feature/bug fix per pull request to keep things clean and easy to understand
### Pull Requesting ###
- Only make pull requests to develop (currently v2-develop), never master, if you make a PR to master we'll comment on it and close it
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it
If you have any questions about any of this, please let us know.

View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 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 General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is 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. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
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.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
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 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. Use with the GNU Affero General Public License.
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 Affero 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 special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU 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 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 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 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 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 General Public License for more details.
You should have received a copy of the GNU 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 the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
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 GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -0,0 +1,132 @@
![OrganizrHeader](https://github.com/causefx/Organizr/raw/v2-develop/plugins/images/organizr/logo-wide.png)
[![Percentage of issues still open](http://isitmaintained.com/badge/open/causefx/Organizr.svg)](http://isitmaintained.com/project/causefx/Organizr "Percentage of issues still open")
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/causefx/Organizr.svg)](http://isitmaintained.com/project/causefx/Organizr "Average time to resolve an issue")
[![GitHub stars](https://img.shields.io/github/stars/causefx/Organizr.svg)](https://github.com/causefx/Organizr/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/causefx/Organizr.svg)](https://github.com/causefx/Organizr/network)
[![Docker pulls](https://img.shields.io/docker/pulls/organizr/organizr.svg)](https://hub.docker.com/r/organizr/organizr)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/causefx)
[![Beerpay](https://beerpay.io/causefx/Organizr/badge.svg?style=beer-square)](https://beerpay.io/causefx/Organizr)
[![Beerpay](https://beerpay.io/causefx/Organizr/make-wish.svg?style=flat-square)](https://beerpay.io/causefx/Organizr?focus=wish)
![OrganizrAbout](https://user-images.githubusercontent.com/16184466/53614282-a91e9e00-3b96-11e9-9b3e-d249775ecaa1.png)
Do you have quite a bit of services running on your computer or server? Do you have a lot of bookmarks or have to memorize a bunch of ip's and ports? Well, Organizr is here to help with that. Organizr allows you to setup "Tabs" that will be loaded all in one webpage. You can then work on your server with ease. Want to give users access to some Tabs? No problem, just enable user support and have them make an account. Want guests to be able to visit too? Enable Guest support for those tabs.
![OrganizrInfo](https://user-images.githubusercontent.com/16184466/53614285-a9b73480-3b96-11e9-835e-9fadd045582b.png)
- PHP 7.2+
- [Official Site](https://organizr.app) - Will be refreshed soon!
- [Official Discord](https://organizr.app/discord)
- [See Wiki](https://docs.organizr.app/) - Will be updated soon!
- [Docker](https://hub.docker.com/r/organizr/organizr)
![OrganizrGallery](https://user-images.githubusercontent.com/16184466/53614284-a9b73480-3b96-11e9-9bea-d7a30b294267.png)
<img src="https://user-images.githubusercontent.com/16184466/53615855-35cc5a80-3b9d-11e9-882b-f09f3eb18173.png" width="23%"></img>
<img src="https://user-images.githubusercontent.com/16184466/53615856-35cc5a80-3b9d-11e9-8428-1f2ae05da2c9.png" width="23%"></img>
<img src="https://user-images.githubusercontent.com/16184466/53615857-35cc5a80-3b9d-11e9-82bf-91987c529e72.png" width="23%"></img>
<img src="https://user-images.githubusercontent.com/16184466/53615858-35cc5a80-3b9d-11e9-8149-01a7fcd9160a.png" width="23%"></img>
[![OrganizrOverview](https://img.youtube.com/vi/LZL4smFB6wU/0.jpg)](https://www.youtube.com/watch?v=LZL4smFB6wU)
![OrganizrFeat](https://user-images.githubusercontent.com/16184466/53614283-a9b73480-3b96-11e9-90ef-6e752e067884.png)
- 'Forgot Password' support [receive an email with your new password, prerequisites: mail server setup]
- Additional language support
- Custom tabs for your services
- Customise the top bar by adding your own site logo or site name
- Enable or disable iFrame for your tabs
- Fail2ban support ([see wiki](https://docs.organizr.app/features/fail2ban-integration))
- Fullscreen Support
- Gravatar Support
- Keyboard shortcut support (Check help tab in settings)
- Login with Plex/Emby/LDAP or sFTP credentials
- Mobile support
- Multiple login support
- Nginx Auth_Request support ([see wiki](https://docs.organizr.app/features/server-authentication))
- Organizr login log viewer
- Personalise any theme: Customise the look and feel of Organizr with access to the colour palette
- Pin/Unpin sidebar
- Protect new user account creation with registration password
- Quick access tabs (access your tabs quickly e.g. www.example.com/#Sonarr)
- Set default page on launch
- Theme-able
- Unlimited User Groups
- Upload new icons with ease
- User management support: Create, delete and promote users from the user management console
- Many more...
![OrganizrFeatReq](https://user-images.githubusercontent.com/16184466/53614286-a9b73480-3b96-11e9-8495-4944b85b1313.png)
[![Feature Requests]](https://vote.organizr.app/)
![OrganizrDocker](https://user-images.githubusercontent.com/16184466/53667702-fcdcc600-3c2e-11e9-8828-860e531e8096.png)
[![Repository](https://img.shields.io/github/stars/organizr/docker-organizr?color=402885&style=for-the-badge&logo=github&logoColor=41add3&)](https://github.com/Organizr/docker-organizr)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/organizr/docker-organizr/Build%20Container?color=402885&style=for-the-badge&logo=github&logoColor=41add3)](https://github.com/organizr/docker-organizr/actions?query=workflow%3A%22Build+Container%22)
[![Docker Pulls](https://img.shields.io/docker/pulls/organizr/organizr?color=402885&style=for-the-badge&logo=docker&logoColor=41add3)](https://hub.docker.com/r/organizr/organizr/)
##### Usage
```bash
docker create \
--name=organizr \
-v <path to data>:/config \
-e PGID=<gid> -e PUID=<uid> \
-p 80:80 \
-e fpm="false" `#optional` \
-e branch="v2-master" `#optional` \
organizr/organizr
```
##### Parameters
The parameters are split into two halves, separated by a colon, the left hand side representing the host and the right the container side. For example with a port -p external:internal - what this shows is the port mapping from internal to external of the container. So `-p 8080:80` would expose port 80 from inside the container to be accessible from the host's IP on port 8080 and `http://192.168.x.x:8080` would show you what's running INSIDE the container on port 80.
- `-p 80` - The port(s)
- `-v /config` - Mapping the config files for Organizr
- `-e PGID` Used for GroupID - see below for link
- `-e PUID` Used for UserID - see below for link
The optional parameters and GID and UID are described in the [readme](https://github.com/Organizr/docker-organizr#parameters) for the container.
##### Info
- Shell access whilst the container is running: `docker exec -it organizr /bin/bash`
- To monitor the logs of the container in realtime: `docker logs -f organizr`
![OrganizrSponsor](https://user-images.githubusercontent.com/16184466/53614287-a9b73480-3b96-11e9-9c8e-e32b4ae20c0d.png)
### Seedboxes.cc
[![Seedboxes.cc](https://user-images.githubusercontent.com/16184466/154811062-201be154-6868-4a24-ade6-a26278935415.png)](https://www.seedboxes.cc)
### BrowserStack for allowing us to use their platform for testing
[![BrowserStack](https://avatars2.githubusercontent.com/u/1119453?s=200&v=4g)](https://www.browserstack.com)
### This project is supported by
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="200px"></img>
## Contributors
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://tronflix.app"><img src="https://avatars.githubusercontent.com/u/22502007?v=4?s=100" width="100px;" alt="Chris Yocum"/><br /><sub><b>Chris Yocum</b></sub></a><br /><a href="#test-tronyx" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://roxedus.dev"><img src="https://avatars.githubusercontent.com/u/7110194?v=4?s=100" width="100px;" alt="Roxedus"/><br /><sub><b>Roxedus</b></sub></a><br /><a href="#test-Roxedus" title="Tests">⚠️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/HalianElf"><img src="https://avatars.githubusercontent.com/u/28244771?v=4?s=100" width="100px;" alt="HalianElf"/><br /><sub><b>HalianElf</b></sub></a><br /><a href="#test-HalianElf" title="Tests">⚠️</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END --

View File

@@ -0,0 +1,16 @@
<?php
class Requests_Auth_Digest extends Requests_Auth_Basic
{
/**
* Set cURL parameters before the data is sent
*
* @param resource $handle cURL resource
*/
public function curl_before_send(&$handle)
{
curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString());
curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY); //CURLAUTH_ANY work with Wowza RESTful
}
}

View File

@@ -0,0 +1,587 @@
<?php
class deluge
{
private $ch;
private $url;
private $request_id;
public $options;
public function __construct($host, $password, $options)
{
$verify = $options['verify'] ?? false;
$this->url = $host . (substr($host, -1) == "/" ? "" : "/") . "json";
$this->request_id = 0;
$this->ch = curl_init($this->url);
switch (gettype($verify)) {
case 'string':
$cert = $options['custom_cert'];
$verify = 2;
break;
case 'NULL':
$cert = $options['organizr_cert'];
$verify = 2;
break;
default:
$cert = $options['organizr_cert'];
$verify = false;
break;
}
$curl_options = array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => array("Accept: application/json", "Content-Type: application/json"),
CURLOPT_ENCODING => "",
CURLOPT_COOKIEJAR => "",
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 10,
CURLOPT_CAINFO => $cert,
CURLOPT_SSL_VERIFYHOST => $verify,
CURLOPT_SSL_VERIFYPEER => $verify,
);
curl_setopt_array($this->ch, $curl_options);
//Log in and get cookies
$result = $this->makeRequest("auth.login", array($password));
if (gettype($result) == 'boolean' && $result !== true) {
throw new Exception("Login failed");
} elseif (gettype($result) == 'string') {
throw new Exception($result);
}
}
/////////////////////////////
//
//webapi functions (https://github.com/idlesign/deluge-webapi)
//
/////////////////////////////
//ids is an array of hashes, or null for all
//params is an array of params:
//active_time, all_time_download, comment, compact, distributed_copies, download_payload_rate, eta, file_priorities, file_progress, files, hash, is_auto_managed, is_finished, is_seed, max_connections, max_download_speed, max_upload_slots, max_upload_speed, message, move_completed, move_completed_path, move_on_completed, move_on_completed_path, name, next_announce, num_files, num_peers, num_pieces, num_seeds, paused, peers, piece_length, prioritize_first_last, private, progress, queue, ratio, remove_at_ratio, save_path, seed_rank, seeding_time, seeds_peers_ratio, state, stop_at_ratio, stop_ratio, time_added, total_done, total_payload_download, total_payload_upload, total_peers, total_seeds, total_size, total_uploaded, total_wanted, tracker, tracker_host, tracker_status, trackers, upload_payload_rate
//or an empty array for all (or null, which returns comment, hash, name, save_path)
public function getTorrents($ids, $params)
{
//if (isset($this->makeRequest("webapi.get_torrents", array($ids, $params))->torrents)) {
return $this->makeRequest("webapi.get_torrents", array($ids, $params))->torrents;
//} else {
//throw new Exception("Login failed");
//}
}
//metainfo is base64 encoded torrent data or a magnet url
//returns a torrent hash or null if the torrent wasn't aded
//params are torrent addition parameters like download_location?
public function addTorrent($metaInfo, $params)
{
return $this->makeRequest("webapi.add_torrent", array($metaInfo, $params));
}
/*implemented in core
public function removeTorrent($hash, $removeData) {
return $this->makeRequest("webapi.remove_torrent", array($hash, $removeData));
}*/
public function getWebAPIVersion()
{
return $this->makeRequest("webapi.get_api_version", array());
}
/////////////////////////////
//
//core functions
//
//parsed from https://web.archive.org/web/20150423162855/http://deluge-torrent.org:80/docs/master/core/rpc.html
//
/////////////////////////////
//Adds a torrent file to the session.
//Parameters:
//filename (string) the filename of the torrent
//filedump (string) a base64 encoded string of the torrent file contents
//options (dict) the options to apply to the torrent on add
public function addTorrentFile($filename, $filedump, $options)
{
return $this->makeRequest("core.add_torrent_file", array($filename, $filedump, $options));
}
//Adds a torrent from a magnet link.
//Parameters:
//uri (string) the magnet link
//options (dict) the options to apply to the torrent on add
public function addTorrentMagnet($uri, $options)
{
return $this->makeRequest("core.add_torrent_magnet", array($uri, $options));
}
//Adds a torrent from a url. Deluge will attempt to fetch the torrentfrom url prior to adding it to the session.
//Parameters:
//url (string) the url pointing to the torrent file
//options (dict) the options to apply to the torrent on add
//headers (dict) any optional headers to send
public function addTorrentUrl($url, $options, $headers)
{
return $this->makeRequest("core.add_torrent_url", array($url, $options, $headers));
}
public function connectPeer($torrentId, $ip, $port)
{
return $this->makeRequest("core.connect_peer", array($torrentId, $ip, $port));
}
public function createTorrent($path, $tracker, $pieceLength, $comment, $target, $webseeds, $private, $createdBy, $trackers, $addToSession)
{
return $this->makeRequest("core.create_torrent", array($path, $tracker, $pieceLength, $comment, $target, $webseeds, $private, $createdBy, $trackers, $addToSession));
}
public function disablePlugin($plugin)
{
return $this->makeRequest("core.disable_plugin", array($plugin));
}
public function enablePlugin($plugin)
{
return $this->makeRequest("core.enable_plugin", array($plugin));
}
public function forceReannounce($torrentIds)
{
return $this->makeRequest("core.force_reannounce", array($torrentIds));
}
//Forces a data recheck on torrent_ids
public function forceRecheck($torrentIds)
{
return $this->makeRequest("core.force_recheck", array($torrentIds));
}
//Returns a list of plugins available in the core
public function getAvailablePlugins()
{
return $this->makeRequest("core.get_available_plugins", array());
}
//Returns a dictionary of the sessions cache status.
public function getCacheStatus()
{
return $this->makeRequest("core.get_cache_status", array());
}
//Get all the preferences as a dictionary
public function getConfig()
{
return $this->makeRequest("core.get_config", array());
}
//Get the config value for key
public function getConfigValue($key)
{
return $this->makeRequest("core.get_config_value", array($key));
}
//Get the config values for the entered keys
public function getConfigValues($keys)
{
return $this->makeRequest("core.get_config_values", array($keys));
}
//Returns a list of enabled plugins in the core
public function getEnabledPlugins()
{
return $this->makeRequest("core.get_enabled_plugins", array());
}
//returns {field: [(value,count)] }for use in sidebar(s)
public function getFilterTree($showZeroHits, $hideCat)
{
return $this->makeRequest("core.get_filter_tree", array($showZeroHits, $hideCat));
}
//Returns the number of free bytes at path
public function getFreeSpace($path)
{
return $this->makeRequest("core.get_free_space", array($path));
}
//Returns the libtorrent version.
public function getLibtorrentVersion()
{
return $this->makeRequest("core.get_libtorrent_version", array());
}
//Returns the active listen port
public function getListenPort()
{
return $this->makeRequest("core.get_listen_port", array());
}
//Returns the current number of connections
public function getNumConnections()
{
return $this->makeRequest("core.get_num_connections", array());
}
public function getPathSize($path)
{
return $this->makeRequest("core.get_path_size", array($path));
}
//Returns a list of torrent_ids in the session.
public function getSessionState()
{
return $this->makeRequest("core.get_session_state", array());
}
//Gets the session status values for keys, these keys are takingfrom libtorrents session status.
public function getSessionStatus($keys)
{
return $this->makeRequest("core.get_session_status", array($keys));
}
public function getTorrentStatus($torrentId, $keys, $diff)
{
return $this->makeRequest("core.get_torrent_status", array($torrentId, $keys, $diff));
}
//returns all torrents , optionally filtered by filter_dict.
public function getTorrentsStatus($filterDict, $keys, $diff)
{
return $this->makeRequest("core.get_torrents_status", array($filterDict, $keys, $diff));
}
public function glob($path)
{
return $this->makeRequest("core.glob", array($path));
}
public function moveStorage($torrentIds, $dest)
{
return $this->makeRequest("core.move_storage", array($torrentIds, $dest));
}
//Pause all torrents in the session
public function pauseAllTorrents()
{
return $this->makeRequest("core.pause_all_torrents", array());
}
public function pauseTorrent($torrentIds)
{
return $this->makeRequest("core.pause_torrent", array($torrentIds));
}
public function queueBottom($torrentIds)
{
return $this->makeRequest("core.queue_bottom", array($torrentIds));
}
public function queueDown($torrentIds)
{
return $this->makeRequest("core.queue_down", array($torrentIds));
}
public function queueTop($torrentIds)
{
return $this->makeRequest("core.queue_top", array($torrentIds));
}
public function queueUp($torrentIds)
{
return $this->makeRequest("core.queue_up", array($torrentIds));
}
//Removes a torrent from the session.
//Parameters:
//torrentId (string) the torrentId of the torrent to remove
//removeData (boolean) if True, remove the data associated with this torrent
public function removeTorrent($torrentId, $removeData)
{
return $this->makeRequest("core.remove_torrent", array($torrentId, $removeData));
}
//Rename files in torrent_id. Since this is an asynchronous operation bylibtorrent, watch for the TorrentFileRenamedEvent to know when thefiles have been renamed.
//Parameters:
//torrentId (string) the torrentId to rename files
//filenames (((index, filename), ...)) a list of index, filename pairs
public function renameFiles($torrentId, $filenames)
{
return $this->makeRequest("core.rename_files", array($torrentId, $filenames));
}
//Renames the folder to new_folder in torrent_id. Watch for theTorrentFolderRenamedEvent which is emitted when the folder has beenrenamed successfully.
//Parameters:
//torrentId (string) the torrent to rename folder in
//folder (string) the folder to rename
//newFolder (string) the new folder name
public function renameFolder($torrentId, $folder, $newFolder)
{
return $this->makeRequest("core.rename_folder", array($torrentId, $folder, $newFolder));
}
//Rescans the plugin folders for new plugins
public function rescanPlugins()
{
return $this->makeRequest("core.rescan_plugins", array());
}
//Resume all torrents in the session
public function resumeAllTorrents()
{
return $this->makeRequest("core.resume_all_torrents", array());
}
public function resumeTorrent($torrentIds)
{
return $this->makeRequest("core.resume_torrent", array($torrentIds));
}
//Set the config with values from dictionary
public function setConfig($config)
{
return $this->makeRequest("core.set_config", array($config));
}
//Sets the auto managed flag for queueing purposes
public function setTorrentAutoManaged($torrentId, $value)
{
return $this->makeRequest("core.set_torrent_auto_managed", array($torrentId, $value));
}
//Sets a torrents file priorities
public function setTorrentFilePriorities($torrentId, $priorities)
{
return $this->makeRequest("core.set_torrent_file_priorities", array($torrentId, $priorities));
}
//Sets a torrents max number of connections
public function setTorrentMaxConnections($torrentId, $value)
{
return $this->makeRequest("core.set_torrent_max_connections", array($torrentId, $value));
}
//Sets a torrents max download speed
public function setTorrentMaxDownloadSpeed($torrentId, $value)
{
return $this->makeRequest("core.set_torrent_max_download_speed", array($torrentId, $value));
}
//Sets a torrents max number of upload slots
public function setTorrentMaxUploadSlots($torrentId, $value)
{
return $this->makeRequest("core.set_torrent_max_upload_slots", array($torrentId, $value));
}
//Sets a torrents max upload speed
public function setTorrentMaxUploadSpeed($torrentId, $value)
{
return $this->makeRequest("core.set_torrent_max_upload_speed", array($torrentId, $value));
}
//Sets the torrent to be moved when completed
public function setTorrentMoveCompleted($torrentId, $value)
{
return $this->makeRequest("core.set_torrent_move_completed", array($torrentId, $value));
}
//Sets the path for the torrent to be moved when completed
public function setTorrentMoveCompletedPath($torrentId, $value)
{
return $this->makeRequest("core.set_torrent_move_completed_path", array($torrentId, $value));
}
//Sets the torrent options for torrent_ids
public function setTorrentOptions($torrentIds, $options)
{
return $this->makeRequest("core.set_torrent_options", array($torrentIds, $options));
}
//Sets a higher priority to the first and last pieces
public function setTorrentPrioritizeFirstLast($torrentId, $value)
{
return $this->makeRequest("core.set_torrent_prioritize_first_last", array($torrentId, $value));
}
//Sets the torrent to be removed at stop_ratio
public function setTorrentRemoveAtRatio($torrentId, $value)
{
return $this->makeRequest("core.set_torrent_remove_at_ratio", array($torrentId, $value));
}
//Sets the torrent to stop at stop_ratio
public function setTorrentStopAtRatio($torrentId, $value)
{
return $this->makeRequest("core.set_torrent_stop_at_ratio", array($torrentId, $value));
}
//Sets the ratio when to stop a torrent if stop_at_ratio is set
public function setTorrentStopRatio($torrentId, $value)
{
return $this->makeRequest("core.set_torrent_stop_ratio", array($torrentId, $value));
}
//Sets a torrents tracker list. trackers will be [{“url”, “tier”}]
public function setTorrentTrackers($torrentId, $trackers)
{
return $this->makeRequest("core.set_torrent_trackers", array($torrentId, $trackers));
}
//Checks if the active port is open
public function testListenPort()
{
return $this->makeRequest("core.test_listen_port", array());
}
public function uploadPlugin($filename, $filedump)
{
return $this->makeRequest("core.upload_plugin", array($filename, $filedump));
}
//Returns a list of the exported methods.
public function getMethodList()
{
return $this->makeRequest("daemon.get_method_list", array());
}
//Returns some info from the daemon.
public function info()
{
return $this->makeRequest("daemon.info", array());
}
public function shutdown(...$params)
{
return $this->makeRequest("daemon.shutdown", $params);
}
/////////////////////////////
//
//web ui functions
//
//parsed from https://web.archive.org/web/20150423143401/http://deluge-torrent.org:80/docs/master/modules/ui/web/json_api.html#module-deluge.ui.web.json_api
//
/////////////////////////////
//Parameters:
//host (string) the hostname
//port (int) the port
//username (string) the username to login as
//password (string) the password to login with
public function addHost($host, $port, $username, $password)
{
return $this->makeRequest("web.add_host", array($host, $port, $username, $password));
}
//Usage
public function addTorrents($torrents)
{
return $this->makeRequest("web.add_torrents", array($torrents));
}
public function connect($hostId)
{
return $this->makeRequest("web.connect", array($hostId));
}
public function connected()
{
return $this->makeRequest("web.connected", array());
}
public function deregisterEventListener($event)
{
return $this->makeRequest("web.deregister_event_listener", array($event));
}
public function disconnect()
{
return $this->makeRequest("web.disconnect", array());
}
public function downloadTorrentFromUrl($url, $cookie)
{
return $this->makeRequest("web.download_torrent_from_url", array($url, $cookie));
}
/* in core
public function getConfig() {
return $this->makeRequest("web.get_config", array());
}*/
public function getEvents()
{
return $this->makeRequest("web.get_events", array());
}
public function getHost($hostId)
{
return $this->makeRequest("web.get_host", array($hostId));
}
public function getHostStatus($hostId)
{
return $this->makeRequest("web.get_host_status", array($hostId));
}
public function getHosts()
{
return $this->makeRequest("web.get_hosts", array());
}
public function getTorrentFiles($torrentId)
{
return $this->makeRequest("web.get_torrent_files", array($torrentId));
}
public function getTorrentInfo($filename)
{
return $this->makeRequest("web.get_torrent_info", array($filename));
}
public function registerEventListener($event)
{
return $this->makeRequest("web.register_event_listener", array($event));
}
public function removeHost($connectionId)
{
return $this->makeRequest("web.remove_host", array($connectionId));
}
/*in core
public function setConfig($config) {
return $this->makeRequest("web.set_config", array($config));
}*/
public function startDaemon($port)
{
return $this->makeRequest("web.start_daemon", array($port));
}
public function stopDaemon($hostId)
{
return $this->makeRequest("web.stop_daemon", array($hostId));
}
//Parameters:
//keys (list) the information about the torrents to gather
//filterDict (dictionary) the filters to apply when selecting torrents.
public function updateUi($keys, $filterDict)
{
return $this->makeRequest("web.update_ui", array($keys, $filterDict));
}
private function makeRequest($method, $params)
{
$post_data = array("id" => $this->request_id, "method" => $method, "params" => $params);
curl_setopt($this->ch, CURLOPT_POSTFIELDS, json_encode($post_data));
$result = curl_exec($this->ch);
if ($result === false) {
throw new Exception("Could not log in due to curl error (no. " . curl_errno($this->ch) . "): " . curl_error($this->ch));
}
$http_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
if ($http_code != 200) {
throw new Exception("Request for method $method returned http code $http_code");
}
$result = json_decode($result);
if (!is_null($result->error)) {
throw new Exception("Method request returned an error (no. " . $result->error->code . "): " . $result->error->message);
}
if ($result->id != $this->request_id) {
throw new Exception("Response id did not match request id");
}
$this->request_id++;
return $result->result;
}
}

View File

@@ -0,0 +1,56 @@
<?php
use Monolog\Handler\SlackWebhookHandler;
use Nekonomokochan\PhpJsonLogger\Logger;
use Nekonomokochan\PhpJsonLogger\LoggerBuilder;
class OrganizrLogger extends LoggerBuilder
{
public $isReady;
/**
* @var SlackWEbhookHandler
*/
private $slackWebhookHandler;
/**
* @return boolean
*/
public function getReadyStatus(): bool
{
return $this->isReady;
}
/**
* @param boolean $readyStatus
*/
public function setReadyStatus(bool $readyStatus)
{
$this->isReady = $readyStatus;
}
public function build(): Logger
{
if (!$this->isReady) {
$this->setChannel('Organizr');
$this->setLogLevel(self::DEBUG);
$this->setMaxFiles(1);
}
return new Logger($this);
}
/**
* @return SlackWebhookHandler
*/
public function getSlackWebhookHandler(): ?SlackWebhookHandler
{
return $this->slackWebhookHandler;
}
/**
* @param SlackWebhookHandler $slackWebhookHandler
*/
public function setSlackWebhookHandler(SlackWebhookHandler $slackWebhookHandler)
{
$this->slackWebhookHandler = $slackWebhookHandler;
}
}

View File

@@ -0,0 +1,4 @@
<?php
/*
* deprecated
*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,328 @@
<?php
class Ping
{
private $host;
private $ttl;
private $timeout;
private $port = 80;
private $data = 'Ping';
private $commandOutput;
/**
* Called when the Ping object is created.
*
* @param string $host
* The host to be pinged.
* @param int $ttl
* Time-to-live (TTL) (You may get a 'Time to live exceeded' error if this
* value is set too low. The TTL value indicates the scope or range in which
* a packet may be forwarded. By convention:
* - 0 = same host
* - 1 = same subnet
* - 32 = same site
* - 64 = same region
* - 128 = same continent
* - 255 = unrestricted
* @param int $timeout
* Timeout (in seconds) used for ping and fsockopen().
* @throws \Exception if the host is not set.
*/
public function __construct($host, $ttl = 255, $timeout = 10)
{
if (!isset($host)) {
throw new \Exception("Error: Host name not supplied.");
}
$this->host = $host;
$this->ttl = $ttl;
$this->timeout = $timeout;
}
/**
* Set the ttl (in hops).
*
* @param int $ttl
* TTL in hops.
*/
public function setTtl($ttl)
{
$this->ttl = $ttl;
}
/**
* Get the ttl.
*
* @return int
* The current ttl for Ping.
*/
public function getTtl()
{
return $this->ttl;
}
/**
* Set the timeout.
*
* @param int $timeout
* Time to wait in seconds.
*/
public function setTimeout($timeout)
{
$this->timeout = $timeout;
}
/**
* Get the timeout.
*
* @return int
* Current timeout for Ping.
*/
public function getTimeout()
{
return $this->timeout;
}
/**
* Set the host.
*
* @param string $host
* Host name or IP address.
*/
public function setHost($host)
{
$this->host = $host;
}
/**
* Get the host.
*
* @return string
* The current hostname for Ping.
*/
public function getHost()
{
return $this->host;
}
/**
* Set the port (only used for fsockopen method).
*
* Since regular pings use ICMP and don't need to worry about the concept of
* 'ports', this is only used for the fsockopen method, which pings servers by
* checking port 80 (by default).
*
* @param int $port
* Port to use for fsockopen ping (defaults to 80 if not set).
*/
public function setPort($port)
{
$this->port = $port;
}
/**
* Get the port (only used for fsockopen method).
*
* @return int
* The port used by fsockopen pings.
*/
public function getPort()
{
return $this->port;
}
/**
* Return the command output when method=exec.
* @return string
*/
public function getCommandOutput()
{
return $this->commandOutput;
}
/**
* Matches an IP on command output and returns.
* @return string
*/
public function getIpAddress()
{
$out = array();
if (preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $this->commandOutput, $out)) {
return $out[0];
}
return null;
}
/**
* Ping a host.
*
* @param string $method
* Method to use when pinging:
* - exec (default): Pings through the system ping command. Fast and
* robust, but a security risk if you pass through user-submitted data.
* - fsockopen: Pings a server on port 80.
* - socket: Creates a RAW network socket. Only usable in some
* environments, as creating a SOCK_RAW socket requires root privileges.
*
* @throws InvalidArgumentException if $method is not supported.
*
* @return mixed
* Latency as integer, in ms, if host is reachable or FALSE if host is down.
*/
public function ping($method = 'exec')
{
$latency = false;
switch ($method) {
case 'exec':
$latency = $this->pingExec();
break;
case 'fsockopen':
$latency = $this->pingFsockopen();
break;
case 'socket':
$latency = $this->pingSocket();
break;
default:
throw new \InvalidArgumentException('Unsupported ping method.');
}
// Return the latency.
return $latency;
}
/**
* The exec method uses the possibly insecure exec() function, which passes
* the input to the system. This is potentially VERY dangerous if you pass in
* any user-submitted data. Be SURE you sanitize your inputs!
*
* @return int
* Latency, in ms.
*/
private function pingExec()
{
$latency = false;
$ttl = escapeshellcmd($this->ttl);
$timeout = escapeshellcmd($this->timeout);
$host = escapeshellcmd($this->host);
// Exec string for Windows-based systems.
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
// -n = number of pings; -i = ttl; -w = timeout (in milliseconds).
$exec_string = 'ping -n 1 -i ' . $ttl . ' -w ' . ($timeout * 1000) . ' ' . $host;
} // Exec string for Darwin based systems (OS X).
else if (strtoupper(PHP_OS) === 'DARWIN') {
// -n = numeric output; -c = number of pings; -m = ttl; -t = timeout.
$exec_string = 'ping -n -c 1 -m ' . $ttl . ' -t ' . $timeout . ' ' . $host;
} // Exec string for other UNIX-based systems (Linux).
else {
// -n = numeric output; -c = number of pings; -t = ttl; -W = timeout
$exec_string = 'ping -n -c 1 -t ' . $ttl . ' -W ' . $timeout . ' ' . $host . ' 2>&1';
}
exec($exec_string, $output, $return);
// Strip empty lines and reorder the indexes from 0 (to make results more
// uniform across OS versions).
$this->commandOutput = implode($output, '');
$output = array_values(array_filter($output));
// If the result line in the output is not empty, parse it.
if (!empty($output[1])) {
// Search for a 'time' value in the result line.
$response = preg_match("/time(?:=|<)(?<time>[\.0-9]+)(?:|\s)ms/", $output[1], $matches);
// If there's a result and it's greater than 0, return the latency.
if ($response > 0 && isset($matches['time'])) {
$latency = round($matches['time'], 2);
}
}
return $latency;
}
/**
* The fsockopen method simply tries to reach the host on a port. This method
* is often the fastest, but not necessarily the most reliable. Even if a host
* doesn't respond, fsockopen may still make a connection.
*
* @return int
* Latency, in ms.
*/
private function pingFsockopen()
{
$start = microtime(true);
// fsockopen prints a bunch of errors if a host is unreachable. Hide those
// irrelevant errors and deal with the results instead.
$fp = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
if (!$fp) {
$latency = false;
} else {
$latency = microtime(true) - $start;
$latency = round($latency * 1000, 2);
}
return $latency;
}
/**
* The socket method uses raw network packet data to try sending an ICMP ping
* packet to a server, then measures the response time. Using this method
* requires the script to be run with root privileges, though, so this method
* only works reliably on Windows systems and on Linux servers where the
* script is not being run as a web user.
*
* @return int
* Latency, in ms.
*/
private function pingSocket()
{
// Create a package.
$type = "\x08";
$code = "\x00";
$checksum = "\x00\x00";
$identifier = "\x00\x00";
$seq_number = "\x00\x00";
$package = $type . $code . $checksum . $identifier . $seq_number . $this->data;
// Calculate the checksum.
$checksum = $this->calculateChecksum($package);
// Finalize the package.
$package = $type . $code . $checksum . $identifier . $seq_number . $this->data;
// Create a socket, connect to server, then read socket and calculate.
if ($socket = socket_create(AF_INET, SOCK_RAW, 1)) {
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array(
'sec' => 10,
'usec' => 0,
));
// Prevent errors from being printed when host is unreachable.
@socket_connect($socket, $this->host, null);
$start = microtime(true);
// Send the package.
@socket_send($socket, $package, strlen($package), 0);
if (socket_read($socket, 255) !== false) {
$latency = microtime(true) - $start;
$latency = round($latency * 1000, 2);
} else {
$latency = false;
}
} else {
$latency = false;
}
// Close the socket.
socket_close($socket);
return $latency;
}
/**
* Calculate a checksum.
*
* @param string $data
* Data for which checksum will be calculated.
*
* @return string
* Binary string checksum of $data.
*/
private function calculateChecksum($data)
{
if (strlen($data) % 2) {
$data .= "\x00";
}
$bit = unpack('n*', $data);
$sum = array_sum($bit);
while ($sum >> 16) {
$sum = ($sum >> 16) + ($sum & 0xffff);
}
return pack('n*', ~$sum);
}
}

View File

@@ -0,0 +1,27 @@
{
"require": {
"dibi/dibi": "^4.2",
"lcobucci/jwt": "^4.1",
"composer/semver": "^1.4",
"phpmailer/phpmailer": "^6.2",
"rmccue/requests": "^1.7",
"kryptonit3/sonarr": "1.0.6.1",
"kryptonit3/couchpotato": "^1.0",
"kryptonit3/sickrage": "^1.0",
"pusher/pusher-php-server": "^4.0",
"pragmarx/google2fa": "^3.0",
"psr/log": "^1.1",
"adldap2/adldap2": "^10.0",
"slim/slim": "^4.0",
"slim/psr7": "^1.1",
"zircote/swagger-php": "^3.0",
"bogstag/oauth2-trakt": "^1.0",
"paquettg/php-html-parser": "^3.1",
"nekonomokochan/php-json-logger": "^1.3",
"bcremer/line-reader": "^1.1",
"peppeocchi/php-cron-scheduler": "^4.0",
"simshaun/recurr": "^5.0",
"stripe/stripe-php": "^7.116",
"ramsey/uuid": "^4.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,774 @@
<?php
return [
'driver' => 'sqlite3',
'dbHost' => '',
'dbUsername' => '',
'dbPassword' => '',
'branch' => 'v2-master',
'authType' => 'internal',
'authBackend' => '',
'authBackendHost' => '',
'authBackendHostPrefix' => '',
'authBackendHostSuffix' => '',
'ldapBindUsername' => '',
'ldapBindPassword' => '',
'ldapSSL' => false,
'ldapTLS' => false,
'authBaseDN' => '',
'authBackendDomain' => '',
'ldapType' => '1',
'logo' => 'plugins/images/organizr/organizr-logo-h.png',
'loginLogo' => 'plugins/images/organizr/organizr-logo-h.png',
'loginWallpaper' => '',
'title' => 'Organizr V2',
'useLogo' => false,
'useLogoLogin' => false,
'headerColor' => '',
'headerTextColor' => '',
'sidebarColor' => '',
'sidebarTextColor' => '',
'accentColor' => '',
'accentTextColor' => '',
'buttonColor' => '',
'buttonTextColor' => '',
'buttonHoverColor' => '',
'buttonTextHoverColor' => '',
'alternateHomepageHeaders' => false,
'lockScreen' => false,
'theme' => 'Organizr',
'style' => 'dark',
'plexURL' => '',
'plexTabURL' => '',
'plexToken' => '',
'plexTabName' => '',
'plexAdmin' => '',
'plexDisableCertCheck' => false,
'plexUseCustomCertificate' => false,
'embyTabURL' => '',
'embyTabName' => '',
'embyURL' => '',
'embyToken' => '',
'embyDisableCertCheck' => false,
'embyUseCustomCertificate' => false,
'jellyfinTabName' => '',
'jellyfinURL' => '',
'jellyfinToken' => '',
'jellyfinSSOURL' => '',
'jellyfinUseCustomCertificate' => false,
'jellyfinDisableCertCheck' => false,
'plexID' => '',
'tautulliURL' => '',
'ombiURL' => '',
'ombiToken' => '',
'ombiAlias' => false,
'ombiFallbackUser' => '',
'ombiFallbackPassword' => '',
'ombiDisableCertCheck' => false,
'ombiUseCustomCertificate' => false,
'overseerrURL' => '',
'overseerrToken' => '',
'overseerrFallbackUser' => '',
'overseerrFallbackPassword' => '',
'overseerrDisableCertCheck' => false,
'overseerrUseCustomCertificate' => false,
'petioURL' => '',
'petioToken' => '',
'petioFallbackUser' => '',
'petioFallbackPassword' => '',
'ssoPlex' => false,
'ssoOmbi' => false,
'ssoTautulli' => false,
'ssoTautulliAuth' => '4',
'ssoJellyfin' => false,
'ssoOverseerr' => false,
'ssoPetio' => false,
'ssoKomga' => false,
'ssoKomgaAuth' => '4',
'komgaURL' => '',
'komgaFallbackUser' => '',
'komgaFallbackPassword' => '',
'komgaSSOMasterPassword' => '',
'sonarrURL' => '',
'sonarrUnmonitored' => false,
'sonarrIcon' => true,
'sonarrCalendarLink' => '',
'sonarrFrameTarget' => '',
'sonarrToken' => '',
'sonarrSocksEnabled' => false,
'sonarrSocksAuth' => '999',
'sonarrDisableCertCheck' => false,
'sonarrUseCustomCertificate' => false,
'lidarrURL' => '',
'lidarrToken' => '',
'lidarrSocksEnabled' => false,
'lidarrSocksAuth' => '999',
'lidarrDisableCertCheck' => false,
'lidarrUseCustomCertificate' => false,
'lidarrIcon' => true,
'lidarrCalendarLink' => '',
'lidarrFrameTarget' => '',
'radarrURL' => '',
'radarrUnmonitored' => false,
'radarrIcon' => true,
'radarrCalendarLink' => '',
'radarrFrameTarget' => '',
'radarrPhysicalRelease' => true,
'radarrDigitalRelease' => false,
'radarrCinemaRelease' => false,
'radarrToken' => '',
'radarrSocksEnabled' => false,
'radarrSocksAuth' => '999',
'radarrDisableCertCheck' => false,
'radarrUseCustomCertificate' => false,
'couchpotatoURL' => '',
'couchpotatoToken' => '',
'couchpotatoDisableCertCheck' => false,
'couchpotatoUseCustomCertificate' => false,
'sickrageURL' => '',
'sickrageToken' => '',
'sickrageDisableCertCheck' => false,
'sickrageUseCustomCertificate' => false,
'jdownloaderURL' => '',
'jdownloaderUsername' => '',
'jdownloaderPassword' => '',
'jdownloaderCombine' => false,
'jdownloaderRefresh' => '60000',
'jdownloaderUseCustomCertificate' => false,
'jdownloaderDisableCertCheck' => false,
'sabnzbdURL' => '',
'sabnzbdToken' => '',
'sabnzbdSocksEnabled' => false,
'sabnzbdSocksAuth' => '999',
'sabnzbdCombine' => false,
'sabnzbdRefresh' => '60000',
'sabnzbdDisableCertCheck' => false,
'sabnzbdUseCustomCertificate' => false,
'nzbgetURL' => '',
'nzbgetUsername' => '',
'nzbgetPassword' => '',
'nzbgetSocksEnabled' => false,
'nzbgetSocksAuth' => '999',
'nzbgetCombine' => false,
'nzbgetRefresh' => '60000',
'nzbgetDisableCertCheck' => false,
'nzbgetUseCustomCertificate' => true,
'transmissionURL' => '',
'transmissionUsername' => '',
'transmissionPassword' => '',
'transmissionHideSeeding' => false,
'transmissionHideCompleted' => false,
'transmissionCombine' => false,
'transmissionRefresh' => '60000',
'transmissionUseCustomCertificate' => false,
'transmissionDisableCertCheck' => true,
'delugeURL' => '',
'delugePassword' => '',
'delugeHideSeeding' => false,
'delugeHideCompleted' => false,
'delugeHideStatus' => true,
'delugeCombine' => false,
'delugeRefresh' => '60000',
'delugeDisableCertCheck' => false,
'delugeUseCustomCertificate' => false,
'qBittorrentURL' => '',
'qBittorrentUsername' => '',
'qBittorrentPassword' => '',
'qBittorrentHideSeeding' => false,
'qBittorrentHideCompleted' => false,
'qBittorrentSortOrder' => 'eta',
'qBittorrentReverseSorting' => false,
'qBittorrentCombine' => false,
'qBittorrentApiVersion' => '1',
'qBittorrentDisableCertCheck' => false,
'qBittorrentUseCustomCertificate' => false,
'qBittorrentRefresh' => '60000',
'qBittorrentSocksEnabled' => false,
'qBittorrentSocksAuth' => '999',
'rTorrentURL' => '',
'uTorrentCombine' => false,
'uTorrentCookie' => '',
'uTorrentDisableCertCheck' => false,
'uTorrentHideCompleted' => false,
'uTorrentHideSeeding' => false,
'uTorrentPassword' => '',
'uTorrentRefresh' => '60000',
'uTorrentToken' => '',
'uTorrentURL' => '',
'uTorrentUseCustomCertificate' => false,
'uTorrentUsername' => '',
'rTorrentURLOverride' => '',
'rTorrentUsername' => '',
'rTorrentPassword' => '',
'rTorrentHideSeeding' => false,
'rTorrentHideCompleted' => false,
'rTorrentSortOrder' => 'datea',
'rTorrentReverseSorting' => false,
'rTorrentCombine' => false,
'rTorrentIgnoreLabel' => '',
'rTorrentDisableCertCheck' => false,
'rTorrentUseCustomCertificate' => false,
'rTorrentLimit' => '200',
'rTorrentRefresh' => '60000',
'homepageJackettEnabled' => false,
'homepageJackettAuth' => '1',
'jackettURL' => '',
'jackettToken' => '',
'jackettUseCustomCertificate' => false,
'jackettDisableCertCheck' => false,
'homepageJackettBackholeDownload' => false,
'homepageProwlarrBackholeDownload' => false,
'homepageProwlarrEnabled' => false,
'homepageProwlarrAuth' => '1',
'ProwlarrURL' => '',
'ProwlarrToken' => '',
'ProwlarrUseCustomCertificate' => false,
'ProwlarrDisableCertCheck' => false,
'homepageProwlarrBackholeDownload' => false,
'homepageCalendarEnabled' => false,
'homepageCalendarAuth' => '4',
'calendariCal' => '',
'homepageCustomHTML01Enabled' => false,
'homepageCustomHTML01Auth' => '1',
'customHTML01' => '',
'homepageCustomHTML02Enabled' => false,
'homepageCustomHTML02Auth' => '1',
'customHTML02' => '',
'homepageCustomHTML03Enabled' => false,
'homepageCustomHTML03Auth' => '1',
'customHTML03' => '',
'homepageCustomHTML04Enabled' => false,
'homepageCustomHTML04Auth' => '1',
'customHTML04' => '',
'homepageCustomHTML05Enabled' => false,
'homepageCustomHTML05Auth' => '1',
'customHTML05' => '',
'homepageCustomHTML06Enabled' => false,
'homepageCustomHTML06Auth' => '1',
'customHTML06' => '',
'homepageCustomHTML07Enabled' => false,
'homepageCustomHTML07Auth' => '1',
'customHTML07' => '',
'homepageCustomHTML08Enabled' => false,
'homepageCustomHTML08Auth' => '1',
'customHTML08' => '',
'homepageDelugeEnabled' => false,
'homepageDelugeAuth' => '1',
'homepageJdownloaderEnabled' => false,
'homepageJdownloaderAuth' => '1',
'homepageSabnzbdEnabled' => false,
'homepageSabnzbdAuth' => '1',
'homepageSonarrEnabled' => false,
'homepageSonarrAuth' => '1',
'homepageSonarrQueueEnabled' => false,
'homepageSonarrQueueAuth' => '1',
'homepageSonarrQueueCombine' => false,
'homepageSonarrQueueRefresh' => '60000',
'homepageLidarrEnabled' => false,
'homepageLidarrAuth' => '1',
'homepageCouchpotatoEnabled' => false,
'homepageCouchpotatoAuth' => '1',
'homepageSickrageEnabled' => false,
'homepageSickrageAuth' => '1',
'homepageRadarrEnabled' => false,
'homepageRadarrAuth' => '1',
'homepageRadarrQueueEnabled' => false,
'homepageRadarrQueueAuth' => '1',
'homepageRadarrQueueCombine' => false,
'homepageRadarrQueueRefresh' => '60000',
'homepageTransmissionEnabled' => false,
'homepageTransmissionAuth' => '1',
'homepageqBittorrentEnabled' => false,
'homepageqBittorrentAuth' => '1',
'homepagerTorrentEnabled' => false,
'homepagerTorrentAuth' => '1',
'homepageuTorrentAuth' => '1',
'homepageuTorrentEnabled' => false,
'homepageNzbgetEnabled' => false,
'homepageNzbgetAuth' => '1',
'homepagePlexEnabled' => false,
'homepagePlexAuth' => '1',
'homepageEmbyEnabled' => false,
'homepageEmbyAuth' => '1',
'homepageJellyfinEnabled' => false,
'homepageJellyfinAuth' => '1',
'homepageOmbiEnabled' => false,
'homepageOmbiAuth' => '1',
'homepageOmbiRequestAuth' => '1',
'homepageOverseerrEnabled' => false,
'homepageOverseerrAuth' => '1',
'homepageOverseerrRequestAuth' => '1',
'homepageJellyfinInstead' => false,
'ombiLimitUser' => false,
'ombiRefresh' => '600000',
'ombiTvDefault' => 'all',
'overseerrLimitUser' => false,
'overseerrRefresh' => '600000',
'overseerrTvDefault' => 'all',
'overseerrPrefer4K' => false,
'homepageWeatherAndAirEnabled' => false,
'homepageWeatherAndAirAuth' => '1',
'homepageWeatherAndAirRefresh' => '3600000',
'homepageWeatherAndAirLatitude' => '',
'homepageWeatherAndAirLongitude' => '',
'homepageWeatherAndAirUnits' => 'imperial',
'homepageWeatherAndAirPollenEnabled' => false,
'homepageWeatherAndAirAirQualityEnabled' => false,
'homepageWeatherAndAirWeatherEnabled' => false,
'homepageWeatherAndAirWeatherHeader' => '',
'homepageWeatherAndAirWeatherHeaderToggle' => false,
'homepageHealthChecksEnabled' => false,
'healthChecksToken' => '',
'healthChecksTags' => '',
'homepageHealthChecksAuth' => '1',
'homepageHealthChecksShowDesc' => false,
'homepageHealthChecksShowTags' => false,
'healthChecksDisableCertCheck' => false,
'healthChecksUseCustomCertificate' => false,
'homepageDonateEnabled' => false,
'homepageDonateAuth' => '999',
'homepageDonatePublicToken' => '',
'homepageDonateSecretToken' => '',
'homepageDonateProductID' => '',
'homepageDonateCustomizeHeading' => 'Donate Please',
'homepageDonateCustomizeDescription' => 'Hi! Help me out with a donation...',
'homepageDonateMinimum' => '100',
'homepageDonateShowUserHistory' => false,
'homepageOrdercustomhtml01' => '1',
'homepageOrdercustomhtml02' => '2',
'homepageOrdertransmission' => '3',
'homepageOrderqBittorrent' => '4',
'homepageOrderdeluge' => '5',
'homepageOrdernzbget' => '6',
'homepageOrdersabnzbd' => '7',
'homepageOrderplexnowplaying' => '8',
'homepageOrderplexrecent' => '9',
'homepageOrderplexplaylist' => '10',
'homepageOrderembynowplaying' => '11',
'homepageOrderembyrecent' => '12',
'homepageOrderombi' => '13',
'homepageOrdercalendar' => '14',
'homepageOrderrTorrent' => '15',
'homepageOrderdownloader' => '16',
'homepageOrderhealthchecks' => '17',
'homepageOrderjdownloader' => '18',
'homepageOrderunifi' => '19',
'homepageOrderPihole' => '20',
'homepageOrdertautulli' => '21',
'homepageOrderMonitorr' => '22',
'homepageOrderWeatherAndAir' => '23',
'homepageOrderSpeedtest' => '24',
'homepageOrderNetdata' => '25',
'homepageOrderSonarrQueue' => '26',
'homepageOrderRadarrQueue' => '27',
'homepageOrderOctoprint' => '28',
'homepageOrderjellyfinnowplaying' => '29',
'homepageOrderjellyfinrecent' => '30',
'homepageOrderJackett' => '31',
'homepageOrdercustomhtml03' => '32',
'homepageOrdercustomhtml04' => '33',
'homepageOrdercustomhtml05' => '34',
'homepageOrdercustomhtml06' => '35',
'homepageOrdercustomhtml07' => '36',
'homepageOrdercustomhtml08' => '37',
'homepageOrderuTorrent' => '38',
'homepageOrderoverseerr' => '39',
'homepageOrderBookmarks' => '40',
'homepageOrderDonate' => '41',
'homepageOrderAdguard' => '42',
'homepageOrderProwlarr' => '43',
'homepageOrderUptimeKuma' => '44',
'homepageOrderPromPage' => '45',
'homepageOrderEmbyLiveTVTracker' => '46',
'homepageOrderUserWatchStats' => '47',
'homepageOrderJellyStat' => '48',
'homepageEmbyLiveTVTrackerEnabled' => false,
'homepageEmbyLiveTVTrackerAuth' => '1',
'homepageEmbyLiveTVTrackerRefresh' => '5',
'homepageEmbyLiveTVTrackerDaysShown' => '7',
'homepageEmbyLiveTVTrackerCompactView' => false,
'homepageEmbyLiveTVTrackerShowDuration' => true,
'homepageEmbyLiveTVTrackerShowSeriesInfo' => true,
'homepageEmbyLiveTVTrackerShowUserInfo' => false,
'homepageEmbyLiveTVTrackerMaxItems' => '10',
'homepageEmbyLiveTVTrackerShowCompleted' => true,
'homepageEmbyLiveTVTrackerMaxCompletedItems' => '5',
'homepageUserWatchStatsEnabled' => false,
'homepageUserWatchStatsAuth' => '1',
'homepageUserWatchStatsRefresh' => '30',
'homepageUserWatchStatsService' => 'plex',
'homepageUserWatchStatsURL' => '',
'homepageUserWatchStatsToken' => '',
'homepageUserWatchStatsDisableCertCheck' => false,
'homepageUserWatchStatsUseCustomCertificate' => false,
'homepageUserWatchStatsDays' => '30',
'homepageUserWatchStatsCompactView' => false,
'homepageUserWatchStatsShowTopUsers' => true,
'homepageUserWatchStatsShowMostWatched' => true,
'homepageUserWatchStatsShowRecentActivity' => true,
'homepageUserWatchStatsMaxItems' => '10',
'homepageUserWatchStatsHeader' => 'User Watch Statistics',
'homepageUserWatchStatsHeaderToggle' => true,
'homepageJellyStatEnabled' => false,
'homepageJellyStatAuth' => '1',
'homepageJellyStatDisplayMode' => 'native',
'jellyStatURL' => '',
'jellyStatInternalURL' => '',
'jellyStatApikey' => '',
'jellyStatDisableCertCheck' => false,
'jellyStatUseCustomCertificate' => false,
'homepageJellyStatRefresh' => '5',
'homepageJellyStatDays' => '30',
'homepageJellyStatShowLibraries' => true,
'homepageJellyStatShowUsers' => true,
'homepageJellyStatShowMostWatched' => true,
'homepageJellyStatShowRecentActivity' => true,
'homepageJellyStatMaxItems' => '10',
'homepageJellyStatShowMostWatchedMovies' => true,
'homepageJellyStatShowMostWatchedShows' => true,
'homepageJellyStatShowMostListenedMusic' => true,
'homepageJellyStatMostWatchedCount' => '10',
'homepageJellyStatIframeHeight' => '800',
'homepageJellyStatIframeScrolling' => true,
'homepageShowStreamNames' => false,
'homepageShowStreamNamesAuth' => '1',
'homepageShowStreamNamesWithoutIp' => false,
'homepageShowStreamNamesWithoutIpAuth' => '1',
'homepageUseCustomStreamNames' => false,
'homepageCustomStreamNames' => '',
'homepageStreamRefresh' => '60000',
'homepageRecentRefresh' => '60000',
'homepageDownloadRefresh' => '60000',
'homepageHealthChecksRefresh' => '60000',
'homepagePlexStreams' => false,
'homepagePlexStreamsAuth' => '1',
'homepagePlexStreamsExclude' => '',
'homepagePlexRecent' => false,
'homepagePlexRecentExclude' => '',
'homepageRecentLimit' => '50',
'homepagePlexRecentAuth' => '1',
'homepagePlexPlaylist' => false,
'homepagePlexPlaylistAuth' => '1',
'homepagePlexSearchExclude' => '',
'homepageEmbyStreams' => false,
'homepageEmbyStreamsAuth' => '1',
'homepageEmbyRecent' => false,
'homepageEmbyRecentAuth' => '1',
'homepageEmbyLink' => ' http://app.emby.media/#!/item/item.html?id={id}&serverId={serverId}',
'homepageJellyfinStreams' => false,
'homepageJellyStreamsAuth' => '1',
'homepageJellyfinRecent' => false,
'homepageJellyfinRecentAuth' => '1',
'homepageJellyfinLink' => 'http://hostname:port/jellyfin/web/index.html#!/details?id={id}&serverId={serverId}',
'homepageBookmarksAuth' => '1',
'homepageBookmarksEnabled' => false,
'homepageBookmarksRefresh' => '3600000',
'calendarDefault' => 'month',
'calendarFirstDay' => '1',
'calendarStart' => '14',
'calendarEnd' => '14',
'calendarRefresh' => '60000',
'calendarTimeFormat' => 'h(:mm)t',
'calendarLimit' => '1000',
'calendarLocale' => 'en',
'customCss' => '',
'customThemeCss' => '',
'mediaSearch' => false,
'mediaSearchType' => '',
'mediaSearchAuth' => '1',
'registrationPassword' => '',
'hideRegistration' => false,
'favIcon' => '',
'pingAuth' => '1',
'pingAuthMessage' => '1',
'adminPingRefresh' => '60000',
'otherPingRefresh' => '600000',
'pingOfflineSound' => 'plugins/sounds/default/open-ended.mp3',
'pingOnlineSound' => 'plugins/sounds/default/awareness.mp3',
'pingMs' => false,
'pingAuthMs' => '1',
'notificationBackbone' => 'izi',
'notificationPosition' => 'br',
'lockoutSystem' => false,
'lockoutTimeout' => '60',
'lockoutMaxAuth' => '0',
'lockoutMinAuth' => '1',
'themeInstalled' => '',
'themeVersion' => '',
'installedPlugins' => [],
'installedThemes' => '',
'authDebug' => false,
'customJava' => '',
'customThemeJava' => '',
'minimalLoginScreen' => false,
'unsortedTabs' => 'top',
'cacheImageSize' => '2',
'plexoAuth' => false,
'statusSounds' => false,
'rememberMeDays' => '7',
'rememberMe' => true,
'plexStrictFriends' => true,
'ignoreTFAIfPlexOAuth' => false,
'debugAreaAuth' => '1',
'commit' => 'n/a',
'ombiLimit' => '50',
'overseerrLimit' => '50',
'localIPFrom' => '',
'localIPTo' => '',
'localIPList' => '',
'sandbox' => 'allow-presentation,allow-forms,allow-same-origin,allow-pointer-lock,allow-scripts,allow-popups,allow-modals,allow-top-navigation,allow-downloads,allow-orientation-lock,allow-popups-to-escape-sandbox,allow-top-navigation-by-user-activation',
'iframeAllow' => 'fullscreen,autoplay,clipboard-read,clipboard-write,camera,microphone,speaker-selection,display-capture,web-share,encrypted-media,picture-in-picture',
'description' => 'Organizr - Accept no others',
'debugErrors' => false,
'healthChecksURL' => 'https://healthchecks.io/api/v1/checks/',
'gaTrackingID' => '',
'loginAttempts' => '5',
'loginLockout' => '60000',
'ombiDefaultFilterAvailable' => true,
'ombiDefaultFilterUnavailable' => true,
'ombiDefaultFilterApproved' => true,
'ombiDefaultFilterUnapproved' => true,
'ombiDefaultFilterDenied' => true,
'overseerrDefaultFilterAvailable' => true,
'overseerrDefaultFilterUnavailable' => true,
'overseerrDefaultFilterApproved' => true,
'overseerrDefaultFilterUnapproved' => true,
'overseerrDefaultFilterDenied' => true,
'selfSignedCert' => '',
'homepagePlexRecentlyAddedMethod' => 'legacy',
'authProxyEnabled' => false,
'authProxyHeaderName' => '',
'authProxyHeaderNameEmail' => '',
'authProxyWhitelist' => '',
'authProxyOverrideLogout' => false,
'authProxyLogoutURL' => '',
'ignoreTFALocal' => false,
'unifiURL' => '',
'unifiUsername' => '',
'unifiPassword' => '',
'unifiSiteName' => '',
'unifiCookie' => '',
'unifiUseCustomCertificate' => false,
'unifiDisableCertCheck' => true,
'homepageUnifiEnabled' => false,
'homepageUnifiAuth' => '1',
'homepageUnifiRefresh' => '600000',
'youtubeAPI' => '',
'wanDomain' => '',
'localAddress' => '',
'enableLocalAddressForward' => false,
'performanceDisableIconDropdown' => false,
'performanceDisableImageDropdown' => false,
'traefikAuthEnable' => false,
'traefikDomainOverride' => '',
'homepageTautulliEnabled' => false,
'homepageTautulliAuth' => '1',
'homepageTautulliLibraryAuth' => '1',
'homepageTautulliLibraryStatsExclude' => '',
'homepageTautulliViewsAuth' => '1',
'homepageTautulliMiscAuth' => '1',
'homepageTautulliViewingStatsExclude' => '',
'homepageTautulliRefresh' => '60000',
'tautulliApikey' => '',
'tautulliLibraries' => true,
'tautulliTopMovies' => true,
'tautulliTopTV' => true,
'tautulliTopUsers' => true,
'tautulliTopPlatforms' => true,
'tautulliPopularMovies' => true,
'tautulliPopularTV' => true,
'tautulliHeader' => 'Tautulli',
'tautulliHeaderToggle' => true,
'tautulliFriendlyName' => true,
'tautulliSocksEnabled' => false,
'tautulliSocksAuth' => '999',
'tautulliUseCustomCertificate' => false,
'tautulliDisableCertCheck' => false,
'homepagePiholeEnabled' => false,
'homepagePiholeAuth' => '1',
'homepagePiholeRefresh' => '10000',
'homepagePiholeCombine' => false,
'piholeHeaderToggle' => true,
'piholeURL' => '',
'piholeToken' => '',
'homepageAdGuardEnabled' => false,
'homepageAdGuardAuth' => '1',
'homepageAdGuardRefresh' => '10000',
'homepageAdGuardCombine' => false,
'adguardQueriesToggle' => true,
'adguardQueriesBlockedToggle' => true,
'adguardPercentToggle' => true,
'adguardProcessingToggle' => true,
'adguardDomainListToggle' => false,
'adguardHeaderToggle' => true,
'adguardURL' => '',
'homepageMonitorrEnabled' => false,
'homepageMonitorrAuth' => '1',
'homepageMonitorrRefresh' => '60000',
'monitorrURL' => '',
'monitorrHeaderToggle' => true,
'monitorrHeader' => 'Monitorr',
'monitorrCompact' => false,
'monitorrDisableCertCheck' => false,
'monitorrUseCustomCertificate' => false,
'homepageSpeedtestEnabled' => false,
'homepageSpeedtestAuth' => '1',
'homepageSpeedtestRefresh' => '1800000',
'speedtestURL' => '',
'speedtestHeaderToggle' => true,
'speedtestHeader' => 'Speedtest',
'speedtestDisableCertCheck' => false,
'speedtestUseCustomCertificate' => false,
'homepageNetdataEnabled' => false,
'homepageNetdataRefresh' => '10000',
'homepageNetdataAuth' => '1',
'netdataDisableCertCheck' => false,
'netdataUseCustomCertificate' => false,
'netdataURL' => '',
'netdata1Title' => '',
'netdata1Chart' => '',
'netdata1Data' => '',
'netdata1Colour' => '',
'netdata1Size' => '',
'netdata1sm' => true,
'netdata1md' => true,
'netdata1lg' => true,
'netdata2Title' => '',
'netdata2Chart' => '',
'netdata2Data' => '',
'netdata2Colour' => '',
'netdata2Size' => '',
'netdata2sm' => true,
'netdata2md' => true,
'netdata2lg' => true,
'netdata3Title' => '',
'netdata3Chart' => '',
'netdata3Data' => '',
'netdata3Colour' => '',
'netdata3Size' => '',
'netdata3sm' => true,
'netdata3md' => true,
'netdata3lg' => true,
'netdata4Title' => '',
'netdata4Chart' => '',
'netdata4Data' => '',
'netdata4Colour' => '',
'netdata4Size' => '',
'netdata4sm' => true,
'netdata4md' => true,
'netdata4lg' => true,
'netdata5Title' => '',
'netdata5Chart' => '',
'netdata5Data' => '',
'netdata5Colour' => '',
'netdata5Size' => '',
'netdata5sm' => true,
'netdata5md' => true,
'netdata5lg' => true,
'netdata6Title' => '',
'netdata6Chart' => '',
'netdata6Data' => '',
'netdata6Colour' => '',
'netdata6Size' => '',
'netdata6sm' => true,
'netdata6md' => true,
'netdata6lg' => true,
'netdata7Title' => '',
'netdata7Chart' => '',
'netdata7Data' => '',
'netdata7Colour' => '',
'netdata7Size' => '',
'netdata7sm' => true,
'netdata7md' => true,
'netdata7lg' => true,
'netdata1Enabled' => false,
'netdata2Enabled' => false,
'netdata3Enabled' => false,
'netdata4Enabled' => false,
'netdata5Enabled' => false,
'netdata6Enabled' => false,
'netdata7Enabled' => false,
'netdataCustom' => '{}',
'homepageOctoprintEnabled' => false,
'homepageOctoprintAuth' => '1',
'homepageOctoprintRefresh' => '10000',
'octoprintURL' => '',
'octoprintToken' => '',
'octoprintHeaderToggle' => true,
'octoprintHeader' => 'Octoprint',
'octoprintDisableCertCheck' => false,
'octoprintUseCustomCertificate' => false,
'githubMenuLink' => true,
'organizrSupportMenuLink' => true,
'organizrDocsMenuLink' => true,
'organizrSignoutMenuLink' => true,
'organizrFeatureRequestLink' => true,
'breezometerToken' => 'd95ab607392d4fa5bf64bb26a5cb2a06',
'customForgotPassText' => '',
'disableRecoverPass' => false,
'expandCategoriesByDefault' => false,
'ignoredNewsIds' => [],
'homepageTraktEnabled' => false,
'homepageTraktAuth' => '1',
'calendarStartTrakt' => '14',
'calendarEndTrakt' => '14',
'traktClientId' => '',
'traktClientSecret' => '',
'traktAccessToken' => '',
'traktAccessTokenExpires' => '',
'traktRefreshToken' => '',
'autoCollapseCategories' => false,
'autoExpandNavBar' => true,
'defaultSettingsTab' => '5',
'blacklisted' => '',
'blacklistedMessage' => 'You have been blacklisted from this site.',
'defaultRequestService' => 'ombi',
'easterEggs' => true,
'allowCollapsableSideMenu' => false,
'sideMenuCollapsed' => false,
'collapseSideMenuOnClick' => false,
'logLevel' => 'INFO',
'maxLogFiles' => '7',
'logLiveUpdateRefresh' => '5000',
'logPageSize' => '50',
'includeDatabaseQueriesInDebug' => false,
'externalPluginMarketplaceRepos' => '',
'externalThemeMarketplaceRepos' => '',
'githubAccessToken' => '',
'checkForPluginUpdate' => true,
'checkForThemeUpdate' => true,
'autoUpdateCronEnabled' => false,
'autoUpdateCronSchedule' => '@weekly',
'autoBackupCronEnabled' => false,
'autoBackupCronSchedule' => '@weekly',
'keepBackupsCountCron' => '20',
'backupLocation' => '',
'useRandomMediaImage' => false,
'sendLogsToSlack' => false,
'slackLogLevel' => 'WARNING',
'slackLogWebhook' => '',
'slackLogWebHookChannel' => '',
'matchUserAgents' => false,
'matchUserIP' => false,
'disableHomepageModals' => false,
'homepageUptimeKumaEnabled' => false,
'homepageUptimeKumaAuth' => '1',
'uptimeKumaURL' => '',
'uptimeKumaToken' => '',
'homepageUptimeKumaRefresh' => '60000',
'homepageUptimeKumaHeader' => 'Uptime Kuma',
'homepageUptimeKumaHeaderToggle' => true,
'homepageUptimeKumaCompact' => true,
'homepageUptimeKumaShowLatency' => true,
'homepagePromPageEnabled' => false,
'promPageURL' => '',
'homepagePromPageRefresh' => '60000',
'homepagePromPageHeader' => 'Status Page',
'homepagePromPageHeaderToggle' => true,
'homepagePromPageCompact' => true,
'homepagePromPageShowUptime' => true,
'checkForUpdate' => true,
'socksDebug' => false,
'maxSocksDebugSize' => 100,
'bypassLoginForLocal' => false,
'localLoginUserId' => "1"
];

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 KiB

View File

@@ -0,0 +1,126 @@
{
"response": {
"result": "success",
"message": null,
"data": {
"content": [
{
"type": "tv",
"title": "Ted Lasso",
"secondaryTitle": "2020",
"summary": "Ted Lasso, an American football coach, moves to England when he\u2019s hired to manage a soccer team\u2014despite having no experience. With cynical players and a doubtful town, will he get them to see the Ted Lasso Way?",
"ratingKey": "718958",
"thumb": "/library/metadata/718958/thumb/1630643017",
"key": "718958-list",
"nowPlayingThumb": "/library/metadata/718958/art/1630643017",
"nowPlayingKey": "718958-np",
"nowPlayingTitle": "Ted Lasso",
"nowPlayingBottom": "2020",
"metadataKey": "718958",
"originalType": {
"0": "show"
},
"uid": "718958",
"elapsed": null,
"duration": 1800000,
"addedAt": 1597470170,
"watched": 0,
"transcoded": "",
"stream": "",
"id": "",
"session": "",
"bandwidth": "",
"bandwidthType": "",
"sessionType": "Direct Playing",
"state": "play",
"user": "",
"userThumb": "",
"userAddress": "",
"address": "https://demo.organizr.app/plex/web/index.html#!/server/fake/details?key=/library/metadata/718958",
"nowPlayingOriginalImage": "api/v2/homepage/image?source=plex&img=/library/metadata/718958/art/1630643017&height=675&width=1200&key=718958-np$R1TLZH1BYF",
"originalImage": "api/v2/homepage/image?source=plex&img=/library/metadata/718958/thumb/1630643017&height=300&width=200&key=718958-list$9T6812YERV",
"openTab": true,
"tabName": "Plex (Watch me)",
"userStream": {
"platform": "",
"product": "",
"device": "",
"stream": "",
"videoResolution": "",
"throttled": false,
"sourceVideoCodec": "",
"videoCodec": "",
"audioCodec": "",
"sourceAudioCodec": "",
"videoDecision": "Direct Play",
"audioDecision": "Direct Play",
"container": "",
"audioChannels": ""
},
"metadata": {
"guid": "plex://show/5e204251072b9e003ca5c140",
"summary": "Ted Lasso, an American football coach, moves to England when he\u2019s hired to manage a soccer team\u2014despite having no experience. With cynical players and a doubtful town, will he get them to see the Ted Lasso Way?",
"rating": "",
"duration": "1800000",
"originallyAvailableAt": "2020-08-14",
"year": "2020",
"studio": "Doozer",
"tagline": "Out of His League",
"genres": [
"Comedy",
"Sport",
"Drama"
],
"actors": [
{
"name": "Jason Sudeikis",
"role": "Ted Lasso",
"thumb": "https://metadata-static.plex.tv/b/people/be1b09bec843d46ad76743326ab0fbd1.jpg"
},
{
"name": "Hannah Waddingham",
"role": "Rebecca Welton",
"thumb": "https://metadata-static.plex.tv/0/people/0a2f43fc49d213f651c0d897d15ebb4e.jpg"
},
{
"name": "Juno Temple",
"role": "Keeley Jones",
"thumb": "https://metadata-static.plex.tv/4/people/486961ff94e5cb952cf3addb2f8aaf4a.jpg"
},
{
"name": "Jeremy Swift",
"role": "Leslie Higgins",
"thumb": "https://metadata-static.plex.tv/people/5d7768253c3c2a001fbca9d2.jpg"
},
{
"name": "Phil Dunster",
"role": "Jamie Tartt",
"thumb": "https://metadata-static.plex.tv/2/people/2dad53cf273295fd29d7d089bb0373ef.jpg"
},
{
"name": "Brett Goldstein",
"role": "Roy Kent",
"thumb": "https://metadata-static.plex.tv/people/5d77682bf54112001f5bc4d3.jpg"
},
{
"name": "Brendan Hunt",
"role": "Coach Beard",
"thumb": "https://metadata-static.plex.tv/people/5d77695e9ab54400214f0e66.jpg"
},
{
"name": "Nick Mohammed",
"role": "Nathan Shelley",
"thumb": "https://metadata-static.plex.tv/c/people/c9b9b5f951dfa5cff88e98e3c8837df4.jpg"
}
]
},
"nowPlayingImageURL": "plugins/images/cache/718958-np.jpg",
"imageURL": "plugins/images/cache/718958-list.jpg"
}
],
"plexID": "fake",
"showNames": true,
"group": "1"
}
}
}

View File

@@ -0,0 +1,218 @@
{
"response": {
"result": "success",
"message": null,
"data": {
"content": [
{
"type": "tv",
"title": "Ted Lasso",
"secondaryTitle": "Season 2 - Episode 9",
"summary": "Beard After Hours",
"ratingKey": "738933",
"thumb": "/library/metadata/738933/thumb/1631895318",
"key": "743046-list",
"nowPlayingThumb": "/library/metadata/718958/art/1630643017",
"nowPlayingKey": "718958-np",
"nowPlayingTitle": "Ted Lasso - Beard After Hours",
"nowPlayingBottom": "S2 \u00b7 E9",
"metadataKey": "718958",
"originalType": {
"0": "episode"
},
"uid": "743046",
"elapsed": 1735000,
"duration": 2583584,
"addedAt": 1631852183,
"watched": 67,
"transcoded": 12,
"stream": "transcode",
"id": "1abe5dd5b977bf954ccd9ccc9f91aebb",
"session": "1abe5dd5b977bf954ccd9ccc9f91aebb",
"bandwidth": "3980",
"bandwidthType": "wan",
"sessionType": "Transcoding",
"state": "play",
"user": "Method Man",
"userThumb": "https://th.bing.com/th/id/R.31a64d5f67dced802bb06e5f4a067b13?rik=imGnVnLBY2iTxg",
"userAddress": "10.0.0.114",
"address": "https://demo.organizr.app/plex/web/index.html#!/server/something/details?key=/library/metadata/743046",
"nowPlayingOriginalImage": "api/v2/homepage/image?source=plex&img=/library/metadata/718958/art/1630643017&height=675&width=1200&key=718958-np$5LNUILHVMC",
"originalImage": "api/v2/homepage/image?source=plex&img=/library/metadata/738933/thumb/1631895318&height=300&width=200&key=743046-list$NE8FV3EVES",
"openTab": true,
"tabName": "Plex (Watch me)",
"userStream": {
"platform": "Roku",
"product": "Plex for Roku",
"device": "100005842",
"stream": "transcode (Throttled)",
"videoResolution": "720p",
"throttled": true,
"sourceVideoCodec": "h264",
"videoCodec": "h264",
"audioCodec": "ac3",
"sourceAudioCodec": "eac3",
"videoDecision": "Transcode",
"audioDecision": "Transcode",
"container": "mpegts",
"audioChannels": "6"
},
"metadata": {
"guid": "plex://episode/613cc0031fe69c7d644b94d0",
"summary": "After the semifinal, Beard sets out on an all-night odyssey through London in an effort to collect his thoughts.",
"rating": "",
"duration": "2583584",
"originallyAvailableAt": "2021-09-17",
"year": "",
"studio": "",
"tagline": "",
"genres": "",
"actors": ""
},
"nowPlayingImageURL": "plugins/images/cache/718958-np.jpg",
"imageURL": "api/v2/homepage/image?source=plex&img=/library/metadata/738933/thumb/1631895318&height=300&width=200&key=743046-list"
},
{
"type": "movie",
"title": "Moana",
"secondaryTitle": "2016",
"summary": "In Ancient Polynesia, when a terrible curse incurred by Maui reaches an impetuous Chieftain's daughter's island, she answers the Ocean's call to seek out the demigod to set things right.",
"ratingKey": "629519",
"thumb": "/library/metadata/629519/thumb/1598244654",
"key": "629519-list",
"nowPlayingThumb": "/library/metadata/629519/art/1598244654",
"nowPlayingKey": "629519-np",
"nowPlayingTitle": "Moana",
"nowPlayingBottom": "2016",
"metadataKey": "629519",
"originalType": {
"0": "movie"
},
"uid": "629519",
"elapsed": 2666000,
"duration": 6432811,
"addedAt": 1487623948,
"watched": 41,
"transcoded": 2,
"stream": "transcode",
"id": "6ejx7gkos73ipivzocta2a61",
"session": "nw0bu3kysxyx6pujqxmsjddm",
"bandwidth": "3978",
"bandwidthType": "wan",
"sessionType": "Transcoding",
"state": "play",
"user": "RZA",
"userThumb": "https://th.bing.com/th/id/R.f8d56c8ff8920ab7bc85cc0ed42d66b0?rik=d%2bZbeC%2bPUcIH0g",
"userAddress": "10.0.0.105",
"address": "https://demo.organizr.app/plex/web/index.html#!/server/asdsadsadsaddas/details?key=/library/metadata/629519",
"nowPlayingOriginalImage": "api/v2/homepage/image?source=plex&img=/library/metadata/629519/art/1598244654&height=675&width=1200&key=629519-np$FKTWEPXEDU",
"originalImage": "api/v2/homepage/image?source=plex&img=/library/metadata/629519/thumb/1598244654&height=300&width=200&key=629519-list$8JSXWW3JLM",
"openTab": true,
"tabName": "Plex (Watch me)",
"userStream": {
"platform": "webOS",
"product": "Plex for LG",
"device": "",
"stream": "transcode",
"videoResolution": "720p",
"throttled": false,
"sourceVideoCodec": "h264",
"videoCodec": "h264",
"audioCodec": "aac",
"sourceAudioCodec": "dca",
"videoDecision": "Transcode",
"audioDecision": "Transcode",
"container": "mpegts",
"audioChannels": "6"
},
"metadata": {
"guid": "plex://movie/5d776af2ad5437001f78dad6",
"summary": "In Ancient Polynesia, when a terrible curse incurred by Maui reaches an impetuous Chieftain's daughter's island, she answers the Ocean's call to seek out the demigod to set things right.",
"rating": "",
"duration": "6432811",
"originallyAvailableAt": "2016-11-07",
"year": "2016",
"studio": "Walt Disney Pictures",
"tagline": "The ocean is calling.",
"genres": [
"Animation",
"Action/Adventure",
"Comedy",
"Family",
"Fantasy",
"Musical",
"Thriller"
],
"actors": [
{
"name": "Auli'i Cravalho",
"role": "Moana (voice)",
"thumb": "https://metadata-static.plex.tv/people/5d776af2ad5437001f78db11.jpg"
},
{
"name": "Dwayne Johnson",
"role": "Maui (voice)",
"thumb": "https://metadata-static.plex.tv/0/people/0740957f34be813b907cc513a4cffe6d.jpg"
},
{
"name": "Rachel House",
"role": "Gramma Tala (voice)",
"thumb": "https://metadata-static.plex.tv/f/people/f7f7556172c45f10222690a5faa74c11.jpg"
},
{
"name": "Temuera Morrison",
"role": "Chief Tui (voice)",
"thumb": "https://metadata-static.plex.tv/4/people/461e9278f779c9589b50075f20d32178.jpg"
},
{
"name": "Jemaine Clement",
"role": "Tamatoa (voice)",
"thumb": "https://metadata-static.plex.tv/people/5d7768322ec6b5001f6bb740.jpg"
},
{
"name": "Nicole Scherzinger",
"role": "Sina (voice)",
"thumb": "https://artworks.thetvdb.com/banners/actors/495988.jpg"
},
{
"name": "Alan Tudyk",
"role": "Heihei / Villager #3 (voice)",
"thumb": "https://metadata-static.plex.tv/e/people/e4f3693273868092da3e44d7c8f4243e.jpg"
},
{
"name": "Christopher Jackson",
"role": "Chief Tui (singing voice)",
"thumb": "https://metadata-static.plex.tv/6/people/6fa8635916eff2a19968707d67e3eb57.jpg"
},
{
"name": "Oscar Kightley",
"role": "Fisherman (voice)",
"thumb": "https://metadata-static.plex.tv/people/5d77683b961905001eb95197.jpg"
},
{
"name": "Troy Polamalu",
"role": "Villager #1 (voice)",
"thumb": "https://metadata-static.plex.tv/people/5d7768883ab0e7001f503de4.jpg"
},
{
"name": "Puanani Cravalho",
"role": "Villager #2 (voice)",
"thumb": "https://metadata-static.plex.tv/people/5d776af2ad5437001f78db13.jpg"
},
{
"name": "Phillipa Soo",
"role": "Unspecified Villager/Sina",
"thumb": "https://metadata-static.plex.tv/f/people/fb5f0ecc0838c82d1c052f41c61725e0.jpg"
}
]
},
"nowPlayingImageURL": "plugins/images/cache/629519-np.jpg",
"imageURL": "api/v2/homepage/image?source=plex&img=/library/metadata/629519/thumb/1598244654&height=300&width=200&key=629519-list"
}
],
"plexID": "sadsadsadsad",
"showNames": true,
"group": "1"
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
<?php
// Set UTC timezone
date_default_timezone_set("UTC");
// Autoload frameworks
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php');
// Include all function and class files
foreach (glob(__DIR__ . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . '*.php') as $filename) {
require_once $filename;
}
foreach (glob(__DIR__ . DIRECTORY_SEPARATOR . 'homepage' . DIRECTORY_SEPARATOR . '*.php') as $filename) {
require_once $filename;
}
// Include EmbyLiveTVTracker trait before class loading
if (file_exists(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'embyLiveTVTracker' . DIRECTORY_SEPARATOR . 'api.php')) {
require_once __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'embyLiveTVTracker' . DIRECTORY_SEPARATOR . 'api.php';
}
foreach (glob(__DIR__ . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . '*.php') as $filename) {
require_once $filename;
}
// Include all pages files
foreach (glob(__DIR__ . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . "*.php") as $filename) {
require_once $filename;
}
// Include all custom pages files
if (file_exists(dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'pages')) {
foreach (glob(dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . '*.php') as $filename) {
require_once $filename;
}
}
// Include all plugin files
try {
$folder = __DIR__ . DIRECTORY_SEPARATOR . 'plugins';
$directoryIterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS);
$iteratorIterator = new RecursiveIteratorIterator($directoryIterator);
foreach ($iteratorIterator as $info) {
if ($info->getFilename() == 'plugin.php' || strpos($info->getFilename(), 'page.php') !== false || $info->getFilename() == 'cron.php') {
require_once $info->getPathname();
}
}
} catch (UnexpectedValueException $e) {
// Folder doesn't exist or permission denied
}
// Include all custom plugin files
try {
if (file_exists(dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'plugins')) {
$folder = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'plugins';
$directoryIterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS);
$iteratorIterator = new RecursiveIteratorIterator($directoryIterator);
foreach ($iteratorIterator as $info) {
if ($info->getFilename() == 'plugin.php' || strpos($info->getFilename(), 'page.php') !== false || $info->getFilename() == 'cron.php') {
require_once $info->getPathname();
}
}
}
} catch (UnexpectedValueException $e) {
// Permission denied
}

View File

@@ -0,0 +1,108 @@
<?php
trait TwoFAFunctions
{
public function create2FA($type)
{
$result['type'] = $type;
switch ($type) {
case 'google':
try {
$google2fa = new PragmaRX\Google2FA\Google2FA();
$google2fa->setAllowInsecureCallToGoogleApis(true);
$result['secret'] = $google2fa->generateSecretKey();
$result['url'] = $google2fa->getQRCodeGoogleUrl(
$this->config['title'],
$this->user['username'],
$result['secret']
);
} catch (PragmaRX\Google2FA\Exceptions\InsecureCallException $e) {
$this->setResponse(500, $e->getMessage());
return null;
}
break;
default:
$this->setAPIResponse('error', $type . ' is not an available to be setup', 404);
return null;
}
$this->setAPIResponse('success', '2FA code created - awaiting verification', 200);
return $result;
}
public function verify2FA($secret, $code, $type)
{
if (!$secret || $secret == '') {
$this->setAPIResponse('error', 'Secret was not supplied or left blank', 422);
return false;
}
if (!$code || $code == '') {
$this->setAPIResponse('error', 'Code was not supplied or left blank', 422);
return false;
}
if (!$type || $type == '') {
$this->setAPIResponse('error', 'Type was not supplied or left blank', 422);
return false;
}
switch ($type) {
case 'google':
$google2fa = new PragmaRX\Google2FA\Google2FA();
$google2fa->setWindow(5);
$valid = $google2fa->verifyKey($secret, $code);
break;
default:
$this->setAPIResponse('error', $type . ' is not an available to be setup', 404);
return false;
}
if ($valid) {
$this->setAPIResponse('success', 'Verification code verified', 200);
return true;
} else {
$this->setAPIResponse('success', 'Verification code invalid', 401);
return false;
}
}
public function save2FA($secret, $type)
{
if (!$secret || $secret == '') {
$this->setAPIResponse('error', 'Secret was not supplied or left blank', 422);
return false;
}
if (!$type || $type == '') {
$this->setAPIResponse('error', 'Type was not supplied or left blank', 422);
return false;
}
$response = [
array(
'function' => 'query',
'query' => array(
'UPDATE users SET',
['auth_service' => $type . '::' . $secret],
'WHERE id = ?',
$this->user['userID']
)
),
];
$this->setLoggerChannel('Users')->info('User added 2FA');
$this->setAPIResponse('success', '2FA Added', 200);
return $this->processQueries($response);
}
public function remove2FA()
{
$response = [
array(
'function' => 'query',
'query' => array(
'UPDATE users SET',
['auth_service' => 'internal'],
'WHERE id = ?',
$this->user['userID']
)
),
];
$this->setLoggerChannel('Users')->info('User removed 2FA');
$this->setAPIResponse('success', '2FA deleted', 204);
return $this->processQueries($response);
}
}

View File

@@ -0,0 +1,88 @@
<?php
class UptimeKumaMetrics
{
protected string $raw;
private array $monitors = [];
public function __construct(string $raw)
{
$this->raw = $raw;
}
public function process(): self
{
$processed = explode(PHP_EOL, $this->raw);
$monitors = array_filter($processed, function (string $item) {
return str_starts_with($item, 'monitor_status');
});
// TODO: parse the latencies and add them on to the info card
$latencies = array_filter($processed, function (string $item) {
return str_starts_with($item, 'monitor_response_time');
});
$monitors = array_map(function (string $item) {
return $this->parseMonitorStatus($item);
}, $monitors);
$this->addLatencyToMonitors($monitors, $latencies);
$this->monitors = array_values(array_filter($monitors));
return $this;
}
public function getMonitors(): array
{
return $this->monitors;
}
private function parseMonitorStatus(string $status): ?array
{
if (substr($status, -1) === '2') {
return null;
}
$up = (substr($status, -1)) == '0' ? false : true;
$status = substr($status, 15);
$status = substr($status, 0, -4);
$status = explode(',', $status);
$data = [
'name' => $this->getStringBetweenQuotes($status[0]),
'url' => $this->getStringBetweenQuotes($status[2]),
'type' => $this->getStringBetweenQuotes($status[1]),
'status' => $up,
];
return $data;
}
private function addLatencyToMonitors(array &$monitors, array $latencies)
{
$latencies = $this->getLatenciesByName($latencies);
foreach ($monitors as &$monitor) {
$monitor['latency'] = $latencies[$monitor['name']] ?? null;
}
}
private function getLatenciesByName(array $latencies): array
{
$l = [];
foreach ($latencies as $latency) {
if (preg_match('/monitor_name="(.*)",monitor_type.* ([0-9]{1,})$/', $latency, $match)) {
$l[$match[1]] = (int) $match[2];
}
continue;
}
return $l;
}
private function getStringBetweenQuotes(string $input): string
{
if (preg_match('/"(.*?)"/', $input, $match) == 1) {
return $match[1];
}
return '';
}
}

View File

@@ -0,0 +1,6 @@
<?php
trait ApiFunctions
{
// Nothing
}

View File

@@ -0,0 +1,541 @@
<?php
trait AuthFunctions
{
public function testConnectionLdap()
{
if (!empty($this->config['authBaseDN']) && !empty($this->config['authBackendHost'])) {
$ad = new \Adldap\Adldap();
// Create a configuration array.
$ldapServers = explode(',', $this->config['authBackendHost']);
$i = 0;
foreach ($ldapServers as $key => $value) {
// Calculate parts
$digest = parse_url(trim($value));
$scheme = strtolower(($digest['scheme'] ?? 'ldap'));
$host = ($digest['host'] ?? ($digest['path'] ?? ''));
$port = ($digest['port'] ?? (strtolower($scheme) == 'ldap' ? 389 : 636));
// Reassign
$ldapHosts[] = $host;
if ($i == 0) {
$ldapPort = $port;
}
$i++;
}
$config = [
// Mandatory Configuration Options
'hosts' => $ldapHosts,
'base_dn' => $this->config['authBaseDN'],
'username' => (empty($this->config['ldapBindUsername'])) ? null : $this->config['ldapBindUsername'],
'password' => (empty($this->config['ldapBindPassword'])) ? null : $this->decrypt($this->config['ldapBindPassword']),
// Optional Configuration Options
'schema' => (($this->config['ldapType'] == '1') ? Adldap\Schemas\ActiveDirectory::class : (($this->config['ldapType'] == '2') ? Adldap\Schemas\OpenLDAP::class : Adldap\Schemas\FreeIPA::class)),
'account_prefix' => '',
'account_suffix' => '',
'port' => $ldapPort,
'follow_referrals' => false,
'use_ssl' => $this->config['ldapSSL'],
'use_tls' => $this->config['ldapTLS'],
'version' => 3,
'timeout' => 5,
// Custom LDAP Options
'custom_options' => [
// See: http://php.net/ldap_set_option
LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_ALLOW
]
];
// Add a connection provider to Adldap.
$ad->addProvider($config);
try {
// If a successful connection is made to your server, the provider will be returned.
$provider = $ad->connect();
} catch (\Adldap\Auth\BindException $e) {
$detailedError = $e->getDetailedError();
$this->setLoggerChannel('LDAP')->error($e);
$this->setAPIResponse('error', $detailedError->getErrorMessage(), 409);
return $detailedError->getErrorMessage();
// There was an issue binding / connecting to the server.
}
if ($provider) {
$this->setAPIResponse('success', 'LDAP connection successful', 200);
return true;
} else {
$this->setAPIResponse('error', 'Could not connect', 500);
return false;
}
return ($provider) ? true : false;
} else {
$this->setAPIResponse('error', 'authBaseDN and/or BackendHost not supplied', 422);
return false;
}
}
public function testConnectionLdapLogin($array)
{
$username = $array['username'] ?? null;
$password = $array['password'] ?? null;
if (empty($username) || empty($password)) {
$this->setAPIResponse('error', 'Username and/or Password not supplied', 422);
return false;
}
if (!empty($this->config['authBaseDN']) && !empty($this->config['authBackendHost'])) {
$ad = new \Adldap\Adldap();
// Create a configuration array.
$ldapServers = explode(',', $this->config['authBackendHost']);
$i = 0;
foreach ($ldapServers as $key => $value) {
// Calculate parts
$digest = parse_url(trim($value));
$scheme = strtolower(($digest['scheme'] ?? 'ldap'));
$host = ($digest['host'] ?? ($digest['path'] ?? ''));
$port = ($digest['port'] ?? (strtolower($scheme) == 'ldap' ? 389 : 636));
// Reassign
$ldapHosts[] = $host;
$ldapServersNew[$key] = $scheme . '://' . $host . ':' . $port; // May use this later
if ($i == 0) {
$ldapPort = $port;
}
$i++;
}
$config = [
// Mandatory Configuration Options
'hosts' => $ldapHosts,
'base_dn' => $this->config['authBaseDN'],
'username' => (empty($this->config['ldapBindUsername'])) ? null : $this->config['ldapBindUsername'],
'password' => (empty($this->config['ldapBindPassword'])) ? null : $this->decrypt($this->config['ldapBindPassword']),
// Optional Configuration Options
'schema' => (($this->config['ldapType'] == '1') ? Adldap\Schemas\ActiveDirectory::class : (($this->config['ldapType'] == '2') ? Adldap\Schemas\OpenLDAP::class : Adldap\Schemas\FreeIPA::class)),
'account_prefix' => (empty($this->config['authBackendHostPrefix'])) ? null : $this->config['authBackendHostPrefix'],
'account_suffix' => (empty($this->config['authBackendHostSuffix'])) ? null : $this->config['authBackendHostSuffix'],
'port' => $ldapPort,
'follow_referrals' => false,
'use_ssl' => $this->config['ldapSSL'],
'use_tls' => $this->config['ldapTLS'],
'version' => 3,
'timeout' => 5,
// Custom LDAP Options
'custom_options' => [
// See: http://php.net/ldap_set_option
LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_ALLOW
]
];
// Add a connection provider to Adldap.
$ad->addProvider($config);
try {
// If a successful connection is made to your server, the provider will be returned.
$provider = $ad->connect();
//prettyPrint($provider);
if ($provider->auth()->attempt($username, $password, true)) {
// Passed.
$user = $provider->search()->find($username);
//return $user->getFirstAttribute('cn');
//return $user->getGroups(['cn']);
//return $user;
//return $user->getUserPrincipalName();
//return $user->getGroups(['cn']);
$this->setResponse(200, 'LDAP connection successful');
return true;
} else {
// Failed.
$this->setResponse(401, 'Username/Password Failed to authenticate');
return false;
}
} catch (\Adldap\Auth\BindException $e) {
$detailedError = $e->getDetailedError();
$this->setLoggerChannel('LDAP')->error($e);
$this->setAPIResponse('error', $detailedError->getErrorMessage(), 500);
return $detailedError->getErrorMessage();
// There was an issue binding / connecting to the server.
} catch (Adldap\Auth\UsernameRequiredException $e) {
$detailedError = $e->getDetailedError();
$this->setLoggerChannel('LDAP')->error($e);
$this->setAPIResponse('error', $detailedError->getErrorMessage(), 422);
return $detailedError->getErrorMessage();
// The user didn't supply a username.
} catch (Adldap\Auth\PasswordRequiredException $e) {
$detailedError = $e->getDetailedError();
$this->setLoggerChannel('LDAP')->error($e);
$this->setAPIResponse('error', $detailedError->getErrorMessage(), 422);
return $detailedError->getErrorMessage();
// The user didn't supply a password.
}
} else {
$this->setAPIResponse('error', 'authBaseDN and/or BackendHost not supplied', 422);
return false;
}
}
public function checkPlexToken($token = '')
{
try {
if (($token !== '')) {
$url = 'https://plex.tv/users/account.json';
$headers = array(
'X-Plex-Token' => $token,
'Content-Type' => 'application/json',
'Accept' => 'application/json'
);
$response = Requests::get($url, $headers);
if ($response->success) {
return json_decode($response->body, true);
}
} else {
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Plex')->error($e);
}
return false;
}
public function checkPlexUser($username)
{
try {
if (!empty($this->config['plexToken'])) {
$url = 'https://plex.tv/api/users';
$headers = array(
'X-Plex-Token' => $this->config['plexToken'],
);
$response = Requests::get($url, $headers);
if ($response->success) {
libxml_use_internal_errors(true);
$userXML = simplexml_load_string($response->body);
if (is_array($userXML) || is_object($userXML)) {
$usernameLower = strtolower($username);
foreach ($userXML as $child) {
if (isset($child['username']) && strtolower($child['username']) == $usernameLower || isset($child['email']) && strtolower($child['email']) == $usernameLower) {
$this->setLoggerChannel('Plex')->info('Found User on Friends List');
$machineMatches = false;
if ($this->config['plexStrictFriends']) {
foreach ($child->Server as $server) {
if ((string)$server['machineIdentifier'] == $this->config['plexID']) {
$machineMatches = true;
}
}
} else {
$machineMatches = true;
}
if ($machineMatches) {
$this->setLoggerChannel('Plex')->info('User Approved for Login');
return true;
} else {
$this->setLoggerChannel('Plex')->warning('User not Approved User');
}
}
}
}
}
}
return false;
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Plex')->error($e);
}
return false;
}
public function plugin_auth_plex($username, $password)
{
try {
$usernameLower = strtolower($username);
//Login User
$url = 'https://plex.tv/users/sign_in.json';
$headers = array(
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded',
'X-Plex-Product' => 'Organizr',
'X-Plex-Version' => '2.0',
'X-Plex-Client-Identifier' => $this->config['uuid'],
);
$data = array(
'user[login]' => $username,
'user[password]' => $password,
);
$options = array('timeout' => 30);
$response = Requests::post($url, $headers, $data, $options);
if ($response->success) {
$json = json_decode($response->body, true);
if ((is_array($json) && isset($json['user']) && isset($json['user']['username'])) && strtolower($json['user']['username']) == $usernameLower || strtolower($json['user']['email']) == $usernameLower) {
if ((!empty($this->config['plexAdmin']) && (strtolower($this->config['plexAdmin']) == strtolower($json['user']['username']) || strtolower($this->config['plexAdmin']) == strtolower($json['user']['email']))) || $this->checkPlexUser($json['user']['username'])) {
return array(
'username' => $json['user']['username'],
'email' => $json['user']['email'],
'image' => $json['user']['thumb'],
'token' => $json['user']['authToken']
);
}
}
}
return false;
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Plex')->error($e);
}
return false;
}
// Pass credentials to LDAP backend
public function plugin_auth_ldap($username, $password)
{
if (!empty($this->config['authBaseDN']) && !empty($this->config['authBackendHost'])) {
$ad = new \Adldap\Adldap();
// Create a configuration array.
$ldapServers = explode(',', $this->config['authBackendHost']);
$i = 0;
foreach ($ldapServers as $key => $value) {
// Calculate parts
$digest = parse_url(trim($value));
$scheme = strtolower((isset($digest['scheme']) ? $digest['scheme'] : 'ldap'));
$host = (isset($digest['host']) ? $digest['host'] : (isset($digest['path']) ? $digest['path'] : ''));
$port = (isset($digest['port']) ? $digest['port'] : (strtolower($scheme) == 'ldap' ? 389 : 636));
// Reassign
$ldapHosts[] = $host;
$ldapServersNew[$key] = $scheme . '://' . $host . ':' . $port; // May use this later
if ($i == 0) {
$ldapPort = $port;
}
$i++;
}
$config = [
// Mandatory Configuration Options
'hosts' => $ldapHosts,
'base_dn' => $this->config['authBaseDN'],
'username' => (empty($this->config['ldapBindUsername'])) ? null : $this->config['ldapBindUsername'],
'password' => (empty($this->config['ldapBindPassword'])) ? null : $this->decrypt($this->config['ldapBindPassword']),
// Optional Configuration Options
'schema' => (($this->config['ldapType'] == '1') ? Adldap\Schemas\ActiveDirectory::class : (($this->config['ldapType'] == '2') ? Adldap\Schemas\OpenLDAP::class : Adldap\Schemas\FreeIPA::class)),
'account_prefix' => (empty($this->config['authBackendHostPrefix'])) ? null : $this->config['authBackendHostPrefix'],
'account_suffix' => (empty($this->config['authBackendHostSuffix'])) ? null : $this->config['authBackendHostSuffix'],
'port' => $ldapPort,
'follow_referrals' => false,
'use_ssl' => $this->config['ldapSSL'],
'use_tls' => $this->config['ldapTLS'],
'version' => 3,
'timeout' => 5,
// Custom LDAP Options
'custom_options' => [
// See: http://php.net/ldap_set_option
LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_ALLOW
]
];
// Add a connection provider to Adldap.
$ad->addProvider($config);
try {
// If a successful connection is made to your server, the provider will be returned.
$provider = $ad->connect();
//prettyPrint($provider);
if ($provider->auth()->attempt($username, $password)) {
try {
// Try and get email from LDAP server
$accountDN = ((empty($this->config['authBackendHostPrefix'])) ? null : $this->config['authBackendHostPrefix']) . $username . ((empty($this->config['authBackendHostSuffix'])) ? null : $this->config['authBackendHostSuffix']);
$record = $provider->search()->findByDnOrFail($accountDN);
$email = $record->getFirstAttribute('mail');
} catch (Adldap\Models\ModelNotFoundException $e) {
// Record wasn't found!
$email = null;
}
// Passed.
return array(
'email' => $email
);
} else {
// Failed.
return false;
}
} catch (\Adldap\Auth\BindException $e) {
$this->setLoggerChannel('LDAP')->error($e);
// There was an issue binding / connecting to the server.
} catch (Adldap\Auth\UsernameRequiredException $e) {
$this->setLoggerChannel('LDAP')->error($e);
// The user didn't supply a username.
} catch (Adldap\Auth\PasswordRequiredException $e) {
$this->setLoggerChannel('LDAP')->error($e);
// The user didn't supply a password.
}
}
return false;
}
// Ldap Auth Missing Dependency
public function plugin_auth_ldap_disabled()
{
return 'LDAP - Disabled (Dependency: php-ldap missing!)';
}
// Pass credentials to FTP backend
public function plugin_auth_ftp($username, $password)
{
// Calculate parts
$digest = parse_url($this->config['authBackendHost']);
$scheme = strtolower((isset($digest['scheme']) ? $digest['scheme'] : (function_exists('ftp_ssl_connect') ? 'ftps' : 'ftp')));
$host = (isset($digest['host']) ? $digest['host'] : (isset($digest['path']) ? $digest['path'] : ''));
$port = (isset($digest['port']) ? $digest['port'] : 21);
// Determine Connection Type
if ($scheme == 'ftps') {
$conn_id = ftp_ssl_connect($host, $port, 20);
} elseif ($scheme == 'ftp') {
$conn_id = ftp_connect($host, $port, 20);
} else {
return false;
}
// Check if valid FTP connection
if ($conn_id) {
// Attempt login
@$login_result = ftp_login($conn_id, $username, $password);
ftp_close($conn_id);
// Return Result
if ($login_result) {
return true;
} else {
return false;
}
} else {
return false;
}
}
// Pass credentials to Emby Backend
public function plugin_auth_emby_local($username, $password)
{
try {
$url = $this->qualifyURL($this->config['embyURL']) . '/Users/AuthenticateByName';
$headers = array(
'Authorization' => 'Emby UserId="e8837bc1-ad67-520e-8cd2-f629e3155721", Client="None", Device="Organizr", DeviceId="xxx", Version="1.0.0.0"',
'Content-Type' => 'application/json',
);
$data = array(
'Username' => $username,
'pw' => $password,
'Password' => sha1($password),
'PasswordMd5' => md5($password),
);
$response = Requests::post($url, $headers, json_encode($data));
if ($response->success) {
$json = json_decode($response->body, true);
if (is_array($json) && isset($json['SessionInfo']) && isset($json['User']) && $json['User']['HasPassword'] == true) {
// Login Success - Now Logout Emby Session As We No Longer Need It
$headers = array(
'X-Emby-Token' => $json['AccessToken'],
'X-Mediabrowser-Token' => $json['AccessToken'],
);
$response = Requests::post($this->qualifyURL($this->config['embyURL']) . '/Sessions/Logout', $headers, array());
if ($response->success) {
return true;
}
}
}
return false;
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Emby')->error($e);
}
return false;
}
// Pass credentials to JellyFin Backend
public function plugin_auth_jellyfin($username, $password)
{
try {
$url = $this->qualifyURL($this->config['jellyfinURL']) . '/Users/authenticatebyname';
$headers = array(
'X-Emby-Authorization' => 'MediaBrowser Client="Organizr Auth", Device="Organizr", DeviceId="orgv2", Version="2.0"',
'Content-Type' => 'application/json',
);
$data = array(
'Username' => $username,
'Pw' => $password
);
$response = Requests::post($url, $headers, json_encode($data));
if ($response->success) {
$json = json_decode($response->body, true);
if (is_array($json) && isset($json['SessionInfo']) && isset($json['User']) && $json['User']['HasPassword'] == true) {
$this->setLoggerChannel('JellyFin')->info('Found User and Logged In');
// Login Success - Now Logout JellyFin Session As We No Longer Need It
$headers = array(
'X-Emby-Authorization' => 'MediaBrowser Client="Organizr Auth", Device="Organizr", DeviceId="orgv2", Version="2.0", Token="' . $json['AccessToken'] . '"',
'Content-Type' => 'application/json',
);
$response = Requests::post($this->qualifyURL($this->config['jellyfinURL']) . '/Sessions/Logout', $headers, array());
if ($response->success) {
return true;
}
}
}
return false;
} catch (Requests_Exception $e) {
$this->setLoggerChannel('JellyFin')->error($e);
}
return false;
}
// Authenticate against emby connect
public function plugin_auth_emby_connect($username, $password)
{
// Emby disabled EmbyConnect on their API
// https://github.com/MediaBrowser/Emby/issues/3553
//return plugin_auth_emby_local($username, $password);
try {
$this->setLoggerChannel('Emby')->info('Attempting to Login with Emby Connect for user: ' . $username);
$connectURL = 'https://connect.emby.media/service/user/authenticate';
$headers = array(
'Accept' => 'application/json',
'X-Application' => 'Organizr/2.0'
);
$data = array(
'nameOrEmail' => $username,
'rawpw' => $password,
);
$response = Requests::post($connectURL, $headers, $data);
if ($response->success) {
$json = json_decode($response->body, true);
if (is_array($json) && isset($json['AccessToken']) && isset($json['User'])) {
$connectUser = $json['User'];
} else {
$this->setLoggerChannel('Emby')->warning('Bad Response');
return false;
}
} else {
$this->setLoggerChannel('Emby')->warning('401 From Emby Connect');
return false;
}
// Get A User
if ($connectUser) {
$url = $this->qualifyURL($this->config['embyURL']) . '/Users?api_key=' . $this->config['embyToken'];
$response = Requests::get($url);
if ($response->success) {
$json = json_decode($response->body, true);
if (is_array($json)) {
foreach ($json as $key => $value) { // Scan for this user
if (isset($value['ConnectUserName']) && isset($value['ConnectLinkType'])) { // Qualify as connect account
if (strtolower($value['ConnectUserName']) == strtolower($connectUser['Name']) || strtolower($value['ConnectUserName']) == strtolower($connectUser['Email'])) {
$this->setLoggerChannel('Emby')->info('Found User');
return array(
'email' => $connectUser['Email'],
'username' => $connectUser['Name']
//'image' => $json['User']['ImageUrl'],
);
}
}
}
}
}
}
return false;
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Emby')->error($e);
return false;
}
}
// Authenticate Against Emby Local (first) and Emby Connect
public function plugin_auth_emby_all($username, $password)
{
// Emby disabled EmbyConnect on their API
// https://github.com/MediaBrowser/Emby/issues/3553
$localResult = $this->plugin_auth_emby_local($username, $password);
//return $localResult;
if ($localResult) {
return $localResult;
} else {
return $this->plugin_auth_emby_connect($username, $password);
}
}
}

View File

@@ -0,0 +1,158 @@
<?php
trait BackupFunctions
{
public function getOrganizrBackupLocation()
{
$defaultPath = $this->config['dbLocation'] . 'backups' . DIRECTORY_SEPARATOR;
$userPath = $this->config['backupLocation'];
if ($this->config['backupLocation'] !== '') {
if (file_exists($userPath)) {
return $userPath;
}
}
return $defaultPath;
}
public function fileArray($files)
{
foreach ($files as $file) {
if (file_exists($file)) {
$list[] = $file;
}
}
if (!empty($list)) {
return $list;
}
}
public function deleteBackup($filename)
{
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$path = $this->getOrganizrBackupLocation();
$filename = $path . $filename;
if ($ext == 'zip') {
if (file_exists($filename)) {
$this->setLoggerChannel('Backup')->info('Deleted Backup [' . pathinfo($filename, PATHINFO_BASENAME) . ']');
$this->setAPIResponse(null, pathinfo($filename, PATHINFO_BASENAME) . ' has been deleted', null);
return (unlink($filename));
} else {
$this->setAPIResponse('error', 'File does not exist', 404);
return false;
}
} else {
$this->setAPIResponse('error', pathinfo($filename, PATHINFO_BASENAME) . ' is not approved to be deleted', 409);
return false;
}
}
public function downloadBackup($filename)
{
$path = $this->getOrganizrBackupLocation();
$filename = $path . $filename;
if (file_exists($filename)) {
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
header('Content-Length: ' . filesize($filename));
flush();
readfile($filename);
exit();
} else {
$this->setAPIResponse('error', 'File does not exist', 404);
return false;
}
}
public function backupOrganizr($type = 'config')
{
$directory = $this->getOrganizrBackupLocation();
@mkdir($directory, 0770, true);
switch ($type) {
case 'config':
break;
case 'full':
break;
default:
}
$this->setLoggerChannel('Backup')->notice('Backing up Organizr');
$zipName = $directory . 'backup[' . date('Y-m-d_H-i') . ' - ' . $this->random_ascii_string(2) . '][' . $this->version . '].zip';
$zip = new ZipArchive;
$zip->open($zipName, ZipArchive::CREATE);
if ($this->config['driver'] == 'sqlite3') {
$zip->addFile($this->config['dbLocation'] . $this->config['dbName'], basename($this->config['dbLocation'] . $this->config['dbName']));
}
$rootPath = $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR;
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($rootPath), RecursiveIteratorIterator::LEAVES_ONLY);
foreach ($files as $name => $file) {
// Skip directories (they would be added automatically)
if (!$file->isDir()) {
if (!stripos($name, 'data' . DIRECTORY_SEPARATOR . 'cache') && !stripos($name, 'backups')) {
// Get real and relative path for current file
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($rootPath));
// Add current file to archive
$zip->addFile($filePath, $relativePath);
}
}
}
$zip->close();
$this->setLoggerChannel('Backup')->notice('Backup process finished');
$this->setAPIResponse('success', 'Backup has been created', 200);
$this->deleteBackupsLimit();
return true;
}
public function deleteBackupsLimit()
{
$backups = $this->getBackups();
if ($backups) {
$list = array_reverse($backups['files']);
$killCount = count($list) - $this->config['keepBackupsCountCron'];
if ($killCount >= 1) {
foreach ($list as $count => $backup) {
$count++;
if ($count <= $killCount) {
$this->log('Cron')->notice('Deleting organizr backup file as it is over limit', ['file' => $backup['name']]);
$this->deleteBackup($backup['name']);
}
}
}
}
return true;
}
public function getBackups()
{
$path = $this->getOrganizrBackupLocation();
@mkdir($path, 0770, true);
$files = array_diff(scandir($path), array('.', '..'));
$fileList = [];
$totalFiles = 0;
$totalFileSize = 0;
foreach ($files as $file) {
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (file_exists($path . $file) && $ext == 'zip') {
$size = filesize($path . $file);
$totalFileSize = $totalFileSize + $size;
$totalFiles = $totalFiles + 1;
try {
$fileList['files'][] = [
'name' => $file,
'size' => $this->human_filesize($size, 0),
'date' => gmdate("Y-m-d\TH:i:s\Z", (filemtime($path . $file)))
];
} catch (Exception $e) {
$this->setAPIResponse('error', 'Backup list failed', 409, $e->getMessage());
return false;
}
}
}
$fileList['total_files'] = $totalFiles;
$fileList['total_size'] = $this->human_filesize($totalFileSize, 2);
$fileList['files'] = $totalFiles > 0 ? array_reverse($fileList['files']) : null;
$this->setAPIResponse('success', null, 200, array_reverse($fileList));
return array_reverse($fileList);
}
}

View File

@@ -0,0 +1 @@
cert file in this directory

View File

@@ -0,0 +1,50 @@
<?php
trait ConfigFunctions
{
public function getConfigItem($item, $term = null)
{
if (strtolower($item) == 'search') {
$configItems = $this->config;
$results = [];
foreach ($configItems as $configItem => $configItemValue) {
if (stripos($configItem, $term) !== false) {
$results[$configItem] = $configItemValue;
if ($configItem == 'organizrHash') {
$results[$configItem] = '***Secure***';
}
}
}
$this->setAPIResponse('success', 'Search results for term: ' . $term, 200, $results);
return $results;
}
if (isset($this->config[$item])) {
$configItem = $this->config[$item];
if ($item == 'organizrHash') {
$configItem = '***Secure***';
}
$this->setAPIResponse('success', 'The value for ' . $item, 200, $configItem);
return $this->config[$item];
} else {
$this->setAPIResponse('error', $item . ' is not defined or is blank', 404);
return false;
}
}
public function getConfigItems()
{
$configItems = $this->config;
/*
foreach ($configItems as $configItem => $configItemValue) {
// should we keep this to filter more items?
if ($configItem == 'organizrHash') {
$configItems[$configItem] = '***Secure***';
}
}
*/
$configItems['organizrHash'] = '***Secure***';
$this->setAPIResponse('success', null, 200, $configItems);
return $configItems;
}
}

View File

@@ -0,0 +1,12 @@
<?php
/* Depreciated */
/*
__
w c(..)o (
\__(-) __)
/\ (
/(_)___)
w /|
| \
rox m m
*/

View File

@@ -0,0 +1,2 @@
<?php
/* Depreciated */

View File

@@ -0,0 +1,25 @@
<?php
/** @noinspection PhpUndefinedFieldInspection */
trait DemoFunctions
{
public function demoData($file = null)
{
if (!$file) {
$this->setResponse(422, 'Demo file was not supplied');
return false;
}
$path = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'demo_data' . DIRECTORY_SEPARATOR . $file;
if (file_exists($path)) {
$data = file_get_contents($path);
$path = (strpos($file, '/') !== false) ? explode('/', $file)[0] . '/' : '';
$data = $this->userDefinedIdReplacementLink($data, ['data/cache/' => 'api/demo_data/' . $path . 'images/']);
$data = json_decode($data, true);
$this->setResponse(200, 'Demo data for file: ' . $file, $data['response']['data']);
return $data['response']['data'];
} else {
$this->setResponse(404, 'Demo data was not found for file: ' . $file);
return false;
}
}
}

View File

@@ -0,0 +1,92 @@
<?php
/** @noinspection PhpUndefinedFieldInspection */
trait HomepageConnectFunctions
{
public function csvHomepageUrlToken($url, $token)
{
$list = array();
$urlList = explode(',', $url);
$tokenList = explode(',', $token);
foreach ($urlList as $key => $value) {
if (isset($tokenList[$key])) {
$list[$key] = array(
'url' => $this->qualifyURL($value),
'token' => $tokenList[$key]
);
}
}
return $list;
}
public function streamType($value)
{
if ($value == "transcode" || $value == "Transcode") {
return "Transcode";
} elseif ($value == "copy" || $value == "DirectStream") {
return "Direct Stream";
} elseif ($value == "directplay" || $value == "DirectPlay") {
return "Direct Play";
} else {
return "Direct Play";
}
}
public function getCacheImageSize($type)
{
switch ($type) {
case 'height':
case 'h':
return round(300 * $this->config['cacheImageSize']);
case 'width':
case 'w':
return round(200 * $this->config['cacheImageSize']);
case 'nowPlayingHeight':
case 'nph':
return round(675 * $this->config['cacheImageSize']);
case 'nowPlayingWidth':
case 'npw':
return round(1200 * $this->config['cacheImageSize']);
}
}
public function ombiImport($type = null)
{
if (!empty($this->config['ombiURL']) && !empty($this->config['ombiToken']) && !empty($type)) {
try {
$url = $this->qualifyURL($this->config['ombiURL']);
$headers = array(
"Accept" => "application/json",
"Content-Type" => "application/json",
"Apikey" => $this->config['ombiToken']
);
$options = ($this->localURL($url)) ? array('verify' => false) : array();
switch ($type) {
case 'emby':
case 'emby_local':
case 'emby_connect':
case 'emby_all':
$response = Requests::post($url . "/api/v1/Job/embyuserimporter", $headers, $options);
break;
case 'plex':
$response = Requests::post($url . "/api/v1/Job/plexuserimporter", $headers, $options);
break;
default:
return false;
break;
}
if ($response->success) {
$this->setLoggerChannel('Ombi')->info('Ran User Import');
return true;
} else {
$this->setLoggerChannel('Ombi')->warning('Unsuccessful connection');
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Ombi')->error($e);
return false;
}
}
return false;
}
}

View File

@@ -0,0 +1,109 @@
<?php
trait HomepageFunctions
{
public function homepageCheckKeyPermissions($key, $permissions)
{
if (array_key_exists($key, $permissions)) {
return $permissions[$key];
} elseif ($key == 'all') {
return $permissions;
} else {
return [];
}
}
public function getHomepageSettingsList()
{
$methods = get_class_methods($this);
$searchTerm = 'SettingsArray';
return array_filter($methods, function ($k) use ($searchTerm) {
return stripos($k, $searchTerm) !== false;
}, 0);
}
public function getHomepageSettingsCombined()
{
$list = $this->getHomepageSettingsList();
$combined = [];
foreach ($list as $item) {
$combined[] = $this->$item(true);
}
return $combined;
}
public function homepageItemPermissions($settings = false, $api = false)
{
if (!$settings) {
if ($api) {
$this->setAPIResponse('error', 'No settings were supplied', 422);
}
return false;
}
foreach ($settings as $type => $setting) {
$settingsType = gettype($setting);
switch ($type) {
case 'enabled':
if ($settingsType == 'string') {
if (!$this->config[$setting]) {
if ($api) {
$this->setAPIResponse('error', $setting . ' module is not enabled', 409);
}
return false;
}
} else {
foreach ($setting as $item) {
if (!$this->config[$item]) {
if ($api) {
$this->setAPIResponse('error', $item . ' module is not enabled', 409);
}
return false;
}
}
}
break;
case 'auth':
if ($settingsType == 'string') {
if (!$this->qualifyRequest($this->config[$setting])) {
if ($api) {
$this->setAPIResponse('error', 'User not approved to view this homepage item', 401);
}
return false;
}
} else {
foreach ($setting as $item) {
if (!$this->qualifyRequest($this->config[$item])) {
if ($api) {
$this->setAPIResponse('error', 'User not approved to view this homepage item', 401);
}
return false;
}
}
}
break;
case 'not_empty':
if ($settingsType == 'string') {
if (empty($this->config[$setting])) {
if ($api) {
$this->setAPIResponse('error', $setting . ' was not supplied', 422);
}
return false;
}
} else {
foreach ($setting as $item) {
if (empty($this->config[$item])) {
if ($api) {
$this->setAPIResponse('error', $item . ' was not supplied', 422);
}
return false;
}
}
}
break;
default:
//return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,479 @@
<?php
trait LogFunctions
{
public function logLocation()
{
return isset($this->config['logLocation']) && $this->config['logLocation'] !== '' ? $this->config['logLocation'] : $this->config['dbLocation'] . 'logs' . DIRECTORY_SEPARATOR;
}
public function debug($msg, $context = [])
{
if ($this->logger) {
$this->logger->debug($msg, $context);
}
}
public function info($msg, $context = [])
{
if ($this->logger) {
$this->logger->info($msg, $context);
}
}
public function notice($msg, $context = [])
{
if ($this->logger) {
$this->logger->notice($msg, $context);
}
}
public function warning($msg, $context = [])
{
if ($this->logger) {
$this->logger->warning($msg, $context);
}
}
public function error($msg, $context = [])
{
if ($this->logger) {
$this->logger->error($msg, $context);
}
}
public function critical($msg, $context = [])
{
if ($this->logger) {
$this->logger->critical($msg, $context);
}
}
public function alert($msg, $context = [])
{
if ($this->logger) {
$this->logger->alert($msg, $context);
}
}
public function emergency($msg, $context = [])
{
if ($this->logger) {
$this->logger->emergency($msg, $context);
}
}
public function setOrganizrLog()
{
if ($this->hasDB()) {
$this->makeDir($this->logLocation());
$logPath = $this->logLocation();
return $logPath . 'organizr.log';
}
return false;
}
public function readLog($file, $pageSize = 10, $offset = 0, $filter = 'NONE', $trace_id = null)
{
$combinedLogs = false;
if ($file == 'combined-logs') {
$combinedLogs = true;
}
if (file_exists($file) || $combinedLogs) {
$filter = strtoupper($filter);
switch ($filter) {
case 'DEBUG':
case 'INFO':
case 'NOTICE':
case 'WARNING':
case 'ERROR':
case 'CRITICAL':
case 'ALERT':
case 'EMERGENCY':
break;
case 'NONE':
$filter = null;
break;
default:
$filter = 'DEBUG';
break;
}
if ($combinedLogs) {
$logs = $this->getLogFiles();
$lines = [];
if ($logs) {
foreach ($logs as $log) {
if (file_exists($log)) {
$lineGenerator = Bcremer\LineReader\LineReader::readLinesBackwards($log);
$lines = array_merge($lines, iterator_to_array($lineGenerator));
}
}
}
} else {
$lineGenerator = Bcremer\LineReader\LineReader::readLinesBackwards($file);
$lines = iterator_to_array($lineGenerator);
}
if ($filter || $trace_id) {
$results = [];
foreach ($lines as $line) {
if ($filter) {
if (stripos($line, '"' . $filter . '"') !== false) {
$results[] = $line;
}
} elseif ($trace_id) {
if (stripos($line, '"' . $trace_id . '"') !== false) {
$results = $line;
}
}
}
$lines = $results;
}
return $this->formatLogResults($lines, $pageSize, $offset);
}
return false;
}
public function formatLogResults($lines, $pageSize, $offset)
{
if (is_array($lines)) {
$totalLines = count($lines);
$totalPages = $totalLines / $pageSize;
$results = array_slice($lines, $offset, $pageSize);
$lines = [];
foreach ($results as $line) {
$lines[] = json_decode($line, true);
}
return [
'pageInfo' => [
'results' => $totalLines,
'totalPages' => ceil($totalPages),
'pageSize' => $pageSize,
'page' => $offset >= $totalPages ? -1 : ceil($offset / $pageSize) + 1
],
'results' => $lines
];
} else {
return json_decode($lines, true);
}
}
public function getLatestLogFile()
{
if ($this->logFile) {
if (isset($this->logFile)) {
$folder = $this->logLocation();
$directoryIterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS);
$iteratorIterator = new RecursiveIteratorIterator($directoryIterator);
$files = [];
foreach ($iteratorIterator as $info) {
$files[] = $info->getPathname();
}
if (count($files) > 0) {
usort($files, function ($x, $y) {
preg_match('/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $x, $xArray);
preg_match('/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $y, $yArray);
return strtotime($xArray[0]) < strtotime($yArray[0]);
});
if (file_exists($files[0])) {
return $files[0];
}
}
}
}
return false;
}
public function getLogFiles()
{
if ($this->logFile) {
if (isset($this->logFile)) {
$folder = $this->logLocation();
$directoryIterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS);
$iteratorIterator = new RecursiveIteratorIterator($directoryIterator);
$files = [];
foreach ($iteratorIterator as $info) {
$files[] = $info->getPathname();
}
if (count($files) > 0) {
usort($files, function ($x, $y) {
preg_match('/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $x, $xArray);
preg_match('/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $y, $yArray);
return strtotime($xArray[0]) < strtotime($yArray[0]);
});
return $files;
}
}
}
return false;
}
public function log(...$params)
{
// Alias of setLoggerChannel
return $this->setLoggerChannel(...$params);
}
public function setLoggerChannel($channel = 'Organizr', $username = null)
{
if ($this->hasDB()) {
$channel = $channel ?: 'Organizr';
$setLogger = false;
if ($username) {
$username = $this->sanitizeUserString($username);
}
if ($this->loggerSetup) {
if ($channel) {
if (strtolower($this->logger->getChannel()) !== strtolower($channel)) {
$this->logger->setChannel($channel);
$setLogger = true;
}
}
if ($username) {
$currentUsername = $this->logger->getTraceId() !== '' ? strtolower($this->logger->getTraceId()) : '';
if ($currentUsername !== strtolower($username)) {
$this->logger->setUsername($username);
$setLogger = true;
}
}
if ($setLogger) {
return $this->setupLogger($channel, $username);
} else {
return $this->logger;
}
} else {
return $this->setupLogger($channel, $username);
}
}
}
public function getLogLevelClass($level, $slack = false)
{
switch ($level) {
case 'DEBUG':
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::DEBUG;
break;
case 'INFO':
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::INFO;
break;
case 'NOTICE':
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::NOTICE;
break;
case 'ERROR':
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::ERROR;
break;
case 'CRITICAL':
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::CRITICAL;
break;
case 'ALERT':
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::ALERT;
break;
case 'EMERGENCY':
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::EMERGENCY;
break;
default:
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::WARNING;
break;
}
if ($slack) {
$organizrLogLevel = $this->getLogLevelClass($this->config['logLevel']);
if ($logLevel < $organizrLogLevel) {
$logLevel = $organizrLogLevel;
}
}
return $logLevel;
}
public function setupLogger($channel = 'Organizr', $username = null)
{
if (!$username) {
$username = $this->user['username'] ?? 'System';
}
$loggerBuilder = new OrganizrLogger();
$loggerBuilder->setReadyStatus($this->hasDB() && $this->logFile);
$loggerBuilder->setMaxFiles($this->config['maxLogFiles']);
$loggerBuilder->setFileName($this->tempLogIfNeeded());
$loggerBuilder->setTraceId($username);
$loggerBuilder->setChannel(ucwords(strtolower($channel)));
$loggerBuilder->setLogLevel($this->getLogLevelClass($this->config['logLevel']));
try {
if ($this->config['sendLogsToSlack']) {
if ($this->config['slackLogWebhook'] !== '') {
$slackHandlerBuilder = new Nekonomokochan\PhpJsonLogger\SlackWebhookHandlerBuilder($this->config['slackLogWebhook'], $this->config['slackLogWebHookChannel']);
$slackHandlerBuilder->setLevel($this->getLogLevelClass($this->config['slackLogLevel'], true));
$loggerBuilder->setSlackWebhookHandler($slackHandlerBuilder->build());
}
}
$this->logger = $loggerBuilder->build();
$this->loggerSetup = true;
return $this->logger;
} catch (Exception $e) {
// nothing so far
return $this->logger;
}
/*
Setup:
set the log channel before you send log (You can set an optional Username (2nd Variable) | If user is logged already logged in, it will use their username):
normal log:
$this->log('Plex Homepage')->info('test');
normal log with context ($context must be an array):
$this->log('Plex Homepage')->info('test', $context);
exception:
$this->log('Plex Homepage')->critical($exception, $context);
*/
}
public function tempLogIfNeeded()
{
if (!$this->logFile) {
return $this->root . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . 'organizr-' . $this->randString() . '.log';
} else {
return $this->logFile;
}
}
public function getLog($pageSize = 10, $offset = 0, $filter = 'NONE', $number = 0, $trace_id = null)
{
if ($this->logFile) {
if (isset($this->logFile)) {
if ($number !== 0) {
if ($number == 'all' || $number == 'combined-logs') {
$log = 'combined-logs';
} else {
$logs = $this->getLogFiles();
$log = $logs[$number] ?? $this->getLatestLogFile();
}
} else {
$log = $this->getLatestLogFile();
}
$readLog = $this->readLog($log, $pageSize, $offset, $filter, $trace_id);
$msg = ($trace_id) ? 'Results for trace_id: ' . $trace_id : 'Results for log: ' . $log;
$this->setResponse(200, $msg, $readLog);
return $readLog;
} else {
$this->setResponse(404, 'Log not found');
return false;
}
} else {
$this->setResponse(409, 'Logging not setup');
return false;
}
}
public function purgeLog($number)
{
$this->setLoggerChannel('Logger');
$this->logger->debug('Starting log purge function');
if ($this->logFile) {
$this->logger->debug('Checking if log id exists');
if ($number !== 0) {
if ($number == 'all' || $number == 'combined-logs') {
$this->logger->debug('Cannot delete log [all] as it is not a real log');
$this->setResponse(409, 'Cannot delete log [all] as it is not a real log');
return false;
}
$logs = $this->getLogFiles();
$file = $logs[$number] ?? false;
if (!$file) {
$this->setResponse(404, 'Log not found');
return false;
}
} else {
$file = $this->getLatestLogFile();
}
preg_match('/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $file, $log);
$log = $log[0];
$this->logger->debug('Checking if log exists');
if (file_exists($file)) {
$this->logger->debug('Log: ' . $log . ' does exist');
$this->logger->debug('Attempting to purge log: ' . $log);
if (unlink($file)) {
$this->logger->info('Log: ' . $log . ' has been purged/deleted');
$this->setResponse(200, 'Log purged');
return true;
} else {
$this->logger->warning('Log: ' . $log . ' could not be purged/deleted');
$this->setResponse(500, 'Log could not be purged');
return false;
}
} else {
$this->logger->debug('Log does not exist');
$this->setResponse(404, 'Log does not exist');
return false;
}
} else {
$this->setResponse(409, 'Logging not setup');
return false;
}
}
public function logArray($context)
{
if (!is_array($context)) {
if (is_string($context)) {
return ['data' => $context];
} else {
$context = (string)$context;
return ['data' => $context];
}
} else {
return $context;
}
}
function buildLogDropdown()
{
$logs = $this->getLogFiles();
//<select class='form-control settings-dropdown-box system-settings-menu'><option value=''>About</option></select>
if ($logs) {
if (count($logs) > 0) {
$options = '';
$i = 0;
foreach ($logs as $k => $log) {
$selected = $i == 0 ? 'selected' : '';
preg_match('/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $log, $name);
$options .= '<option data-id="' . $k . '" value="api/v2/log/' . $k . '?filter=NONE&pageSize=1000&offset=0" ' . $selected . '>' . $name[0] . '</option>';
$i++;
}
return '<select class="form-control choose-organizr-log"><option data-id="all" value="api/v2/log/all?filter=NONE&pageSize=1000&offset=0">All</option>' . $options . '</select>';
}
}
return false;
}
function buildFilterDropdown()
{
$dropdownItems = '<li><a href="javascript:toggleLogFilter(\'DEBUG\')"><span lang="en">Debug</span></a></li>';
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'INFO\')"><span lang="en">Info</span></a></li>';
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'NOTICE\')"><span lang="en">Notice</span></a></li>';
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'WARNING\')"><span lang="en">Warning</span></a></li>';
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'ERROR\')"><span lang="en">Error</span></a></li>';
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'CRITICAL\')"><span lang="en">Critical</span></a></li>';
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'ALERT\')"><span lang="en">Alert</span></a></li>';
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'EMERGENCY\')"><span lang="en">Emergency</span></a></li>';
$dropdownItems .= '<li class="divider"></li><li><a href="javascript:toggleLogFilter(\'NONE\')"><span lang="en">None</span></a></li>';
return '<button aria-expanded="false" data-toggle="dropdown" class="btn btn-inverse dropdown-toggle waves-effect waves-light pull-right m-r-5 hidden-xs" type="button"> <span class="log-filter-text m-r-5" lang="en">NONE</span><i class="fa fa-filter m-r-5"></i></button><ul role="menu" class="dropdown-menu log-filter-dropdown pull-right">' . $dropdownItems . '</ul>';
}
public function testConnectionSlackLogs()
{
if (!$this->config['sendLogsToSlack']) {
$this->setResponse(409, 'sendLogsToSlack is disabled');
return false;
}
if ($this->config['slackLogWebhook'] == '') {
$this->setResponse(409, 'slackLogWebhook is empty');
return false;
}
if ($this->config['slackLogWebHookChannel'] == '' && stripos($this->config['slackLogWebhook'], 'discord') === false) {
$this->setResponse(409, 'slackLogWebhook is empty');
return false;
}
$context = [
'test' => 'success',
];
$this->setupLogger('Slack Tester', $this->user['username'])->warning('Warning Test', $context);
$this->setResponse(200, 'Slack test connection completed - Please check Slack/Discord Channel');
return true;
}
}

View File

@@ -0,0 +1,6 @@
<?php
trait NetDataFunctions
{
}

View File

@@ -0,0 +1,917 @@
<?php
trait NormalFunctions
{
public function formatSeconds($seconds)
{
$hours = 0;
$milliseconds = str_replace("0.", '', $seconds - floor($seconds));
if ($seconds > 3600) {
$hours = floor($seconds / 3600);
}
$seconds = $seconds % 3600;
$time = str_pad($hours, 2, '0', STR_PAD_LEFT)
. gmdate(':i:s', $seconds)
. ($milliseconds ? '.' . $milliseconds : '');
$parts = explode(':', $time);
$timeExtra = explode('.', $parts[2]);
if ($parts[0] !== '00') { // hours
return $time;
} elseif ($parts[1] !== '00') { // mins
return $parts[1] . 'min(s) ' . $timeExtra[0] . 's';
} elseif ($timeExtra[0] !== '00') { // secs
return substr($parts[2], 0, 5) . 's | ' . substr($parts[2], 0, 7) * 1000 . 'ms';
} else {
return substr($parts[2], 0, 7) * 1000 . 'ms';
}
//return $timeExtra[0] . 's ' . (number_format(('0.' . substr($timeExtra[1], 0, 4)), 4, '.', '') * 1000) . 'ms';
//return (number_format(('0.' . substr($timeExtra[1], 0, 4)), 4, '.', '') * 1000) . 'ms';
}
public function getExtension($string)
{
return preg_replace("#(.+)?\.(\w+)(\?.+)?#", "$2", $string);
}
public function get_browser_name()
{
$user_agent = $_SERVER['HTTP_USER_AGENT'];
if (strpos($user_agent, 'Opera') || strpos($user_agent, 'OPR/')) {
return 'Opera';
} elseif (strpos($user_agent, 'Edge')) {
return 'Edge';
} elseif (strpos($user_agent, 'Chrome')) {
return 'Chrome';
} elseif (strpos($user_agent, 'Safari')) {
return 'Safari';
} elseif (strpos($user_agent, 'Firefox')) {
return 'Firefox';
} elseif (strpos($user_agent, 'MSIE') || strpos($user_agent, 'Trident/7')) {
return 'Internet Explorer';
}
return 'Other';
}
public function array_filter_key(array $array, $callback)
{
$matchedKeys = array_filter(array_keys($array), $callback);
return array_intersect_key($array, array_flip($matchedKeys));
}
public function getOS()
{
if (PHP_SHLIB_SUFFIX == "dll") {
return "win";
} else {
return "*nix";
}
}
// Get Gravatar Email Image
public function gravatar($email = '')
{
$email = md5(strtolower(trim($email)));
return "https://www.gravatar.com/avatar/$email?s=100&d=mm";
}
// Clean Directory string
public function cleanDirectory($path)
{
$path = str_replace(array('/', '\\'), '/', $path);
if (substr($path, -1) != '/') {
$path = $path . '/';
}
if ($path[0] != '/' && $path[1] != ':') {
$path = '/' . $path;
}
return $path;
}
// Print output all purrty
public function prettyPrint($v, $error = false)
{
if ($error) {
$background = 'red';
$border = 'black';
$text = 'white';
} else {
$background = '#f2f2f2';
$border = 'black';
$text = 'black';
}
$trace = debug_backtrace()[0];
echo '<pre style="white-space: pre; text-overflow: ellipsis; overflow: hidden; color: ' . $text . '; background-color: ' . $background . '; border: 2px solid ' . $border . '; border-radius: 5px; padding: 5px; margin: 5px;">' . $trace['file'] . ':' . $trace['line'] . ' ' . gettype($v) . "\n\n" . print_r($v, 1) . '</pre><br/>';
}
public function gen_uuid()
{
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
// 16 bits for "time_mid"
mt_rand(0, 0xffff),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
mt_rand(0, 0x0fff) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
mt_rand(0, 0x3fff) | 0x8000,
// 48 bits for "node"
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
public function dbExtension($string)
{
return (substr($string, -3) == '.db') ? $string : $string . '.db';
}
public function removeDbExtension($string)
{
return substr($string, 0, -3);
}
public function cleanPath($path)
{
$path = preg_replace('/([^:])(\/{2,})/', '$1/', $path);
$path = rtrim($path, '/');
return $path;
}
public function searchArray($array, $field, $value)
{
foreach ($array as $key => $item) {
if ($item[$field] === $value)
return $key;
}
return false;
}
public function localURL($url, $force = false)
{
if ($force) {
return true;
}
if (strpos($url, 'https') !== false) {
preg_match("/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/", $url, $result);
$result = !empty($result);
return $result;
}
return false;
}
public function arrayIP($string)
{
if (strpos($string, ',') !== false) {
$result = explode(",", $string);
} else {
$result = array($string);
}
foreach ($result as &$ip) {
$ip = is_numeric(substr($ip, 0, 1)) ? $ip : gethostbyname($ip);
}
return $result;
}
public function timeExecution($previous = null)
{
if (!$previous) {
return microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
} else {
return (microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]) - $previous;
}
}
public function getallheaders()
{
if (!function_exists('getallheaders')) {
function getallheaders()
{
$headers = array();
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
return $headers;
}
} else {
return getallheaders();
}
}
public function getallheadersi()
{
return array_change_key_case($this->getallheaders(), CASE_LOWER);
}
public function random_ascii_string($length)
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
// Generate Random string
public function randString($length = 10, $chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
$tmp = '';
for ($i = 0; $i < $length; $i++) {
$tmp .= substr(str_shuffle($chars), 0, 1);
}
return $tmp;
}
public function isEncrypted($password = null)
{
return ($password == null || $password == '') ? false : $this->decrypt($password);
}
public function fillString($string, $length)
{
$filler = '0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*';
if (strlen($string) < $length) {
$diff = $length - strlen($string);
$filler = substr($filler, 0, $diff);
return $string . $filler;
} elseif (strlen($string) > $length) {
return substr($string, 0, $length);
} else {
return $string;
}
}
public function userIP()
{
if (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ipaddress = $_SERVER['HTTP_CLIENT_IP'];
} elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (isset($_SERVER['HTTP_X_FORWARDED'])) {
$ipaddress = $_SERVER['HTTP_X_FORWARDED'];
} elseif (isset($_SERVER['HTTP_FORWARDED_FOR'])) {
$ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
} elseif (isset($_SERVER['HTTP_FORWARDED'])) {
$ipaddress = $_SERVER['HTTP_FORWARDED'];
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ipaddress = $_SERVER['REMOTE_ADDR'];
} else {
$ipaddress = '127.0.0.1';
}
if (strpos($ipaddress, ',') !== false) {
list($first, $last) = explode(",", $ipaddress);
unset($last);
return $first;
} else {
return $ipaddress;
}
}
public function serverIP()
{
if (array_key_exists('SERVER_ADDR', $_SERVER)) {
return $_SERVER['SERVER_ADDR'];
}
return '127.0.0.1';
}
public function parseDomain($value, $force = false)
{
$badDomains = array('ddns.net', 'ddnsking.com', '3utilities.com', 'bounceme.net', 'freedynamicdns.net', 'freedynamicdns.org', 'gotdns.ch', 'hopto.org', 'myddns.me', 'myds.me', 'myftp.biz', 'myftp.org', 'myvnc.com', 'noip.com', 'onthewifi.com', 'redirectme.net', 'serveblog.net', 'servecounterstrike.com', 'serveftp.com', 'servegame.com', 'servehalflife.com', 'servehttp.com', 'serveirc.com', 'serveminecraft.net', 'servemp3.com', 'servepics.com', 'servequake.com', 'sytes.net', 'viewdns.net', 'webhop.me', 'zapto.org');
$Domain = $value;
$Port = strpos($Domain, ':');
if ($Port !== false) {
$Domain = substr($Domain, 0, $Port);
$value = $Domain;
}
$check = substr_count($Domain, '.');
if ($check >= 3) {
if (is_numeric($Domain[0])) {
$Domain = '';
} else {
if (in_array(strtolower(explode('.', $Domain)[2] . '.' . explode('.', $Domain)[3]), $badDomains)) {
$Domain = '.' . explode('.', $Domain)[0] . '.' . explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2] . '.' . explode('.', $Domain)[3];
} else {
$Domain = '.' . explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2] . '.' . explode('.', $Domain)[3];
}
}
} elseif ($check == 2) {
if (in_array(strtolower(explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2]), $badDomains)) {
$Domain = '.' . explode('.', $Domain)[0] . '.' . explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2];
} elseif (explode('.', $Domain)[0] == 'www') {
$Domain = '.' . explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2];
} elseif (explode('.', $Domain)[1] == 'co') {
$Domain = '.' . explode('.', $Domain)[0] . '.' . explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2];
} else {
$Domain = '.' . explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2];
}
} elseif ($check == 1) {
$Domain = '.' . $Domain;
} else {
$Domain = '';
}
return ($force) ? $value : $Domain;
}
// Cookie Custom Function
public function coookie($type, $name, $value = '', $days = -1, $http = true, $path = '/')
{
$days = ($days > 365) ? 365 : $days;
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
$Secure = true;
$HTTPOnly = true;
} elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' && $_SERVER['HTTPS'] !== '') {
$Secure = true;
$HTTPOnly = true;
} else {
$Secure = false;
$HTTPOnly = false;
}
if (!$http) {
$HTTPOnly = false;
}
$_SERVER['HTTP_HOST'] = $_SERVER['HTTP_HOST'] ?? '';
$Domain = $this->parseDomain($_SERVER['HTTP_HOST']);
$DomainTest = $this->parseDomain($_SERVER['HTTP_HOST'], true);
if ($type == 'set') {
$_COOKIE[$name] = $value;
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + (86400 * $days)) . ' GMT')
. (empty($path) ? '' : '; path=' . $path)
. (empty($Domain) ? '' : '; domain=' . $Domain)
. (!$Secure ? '' : '; SameSite=None; Secure')
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + (86400 * $days)) . ' GMT')
. (empty($path) ? '' : '; path=' . $path)
. (empty($Domain) ? '' : '; domain=' . $DomainTest)
. (!$Secure ? '' : '; SameSite=None; Secure')
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
} elseif ($type == 'delete') {
unset($_COOKIE[$name]);
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
. (empty($path) ? '' : '; path=' . $path)
. (empty($Domain) ? '' : '; domain=' . $Domain)
. (!$Secure ? '' : '; SameSite=None; Secure')
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
. (empty($path) ? '' : '; path=' . $path)
. (empty($Domain) ? '' : '; domain=' . $DomainTest)
. (!$Secure ? '' : '; SameSite=None; Secure')
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
}
}
public function coookieSeconds($type, $name, $value = '', $ms = null, $http = true, $path = '/')
{
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
$Secure = true;
$HTTPOnly = true;
} elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' && $_SERVER['HTTPS'] !== '') {
$Secure = true;
$HTTPOnly = true;
} else {
$Secure = false;
$HTTPOnly = false;
}
if (!$http) {
$HTTPOnly = false;
}
$_SERVER['HTTP_HOST'] = $_SERVER['HTTP_HOST'] ?? '';
$Domain = $this->parseDomain($_SERVER['HTTP_HOST']);
$DomainTest = $this->parseDomain($_SERVER['HTTP_HOST'], true);
if ($type == 'set') {
$_COOKIE[$name] = $value;
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + ($ms / 1000)) . ' GMT')
. (empty($path) ? '' : '; path=' . $path)
. (empty($Domain) ? '' : '; domain=' . $Domain)
. (!$Secure ? '' : '; SameSite=None; Secure')
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + ($ms / 1000)) . ' GMT')
. (empty($path) ? '' : '; path=' . $path)
. (empty($Domain) ? '' : '; domain=' . $DomainTest)
. (!$Secure ? '' : '; SameSite=None; Secure')
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
} elseif ($type == 'delete') {
unset($_COOKIE[$name]);
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
. (empty($path) ? '' : '; path=' . $path)
. (empty($Domain) ? '' : '; domain=' . $Domain)
. (!$Secure ? '' : '; SameSite=None; Secure')
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
. (empty($path) ? '' : '; path=' . $path)
. (empty($Domain) ? '' : '; domain=' . $DomainTest)
. (!$Secure ? '' : '; SameSite=None; Secure')
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
}
}
// Qualify URL
public function qualifyURL($url, $return = false, $includeTrailing = false)
{
//local address?
if (substr($url, 0, 1) == "/") {
if ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || (isset($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] != 'off') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] != 'http')) {
$protocol = "https://";
} else {
$protocol = "http://";
}
$url = $protocol . $this->getServer() . $url;
}
// Get Digest
$digest = $includeTrailing ? parse_url($url) : parse_url(rtrim(preg_replace('/\s+/', '', $url), '/'));
// http/https
if (!isset($digest['scheme'])) {
$scheme = 'http';
} else {
$scheme = $digest['scheme'];
}
// Host
$host = ($digest['host'] ?? '');
// Port
$port = (isset($digest['port']) ? ':' . $digest['port'] : '');
// Path
$path = ($digest['path'] ?? '');
// Query
$query = (isset($digest['query']) ? '?' . $digest['query'] : '');
// Fragment
$fragment = (isset($digest['fragment']) ? '#' . $digest['fragment'] : '');
// Output
$array = [
'scheme' => $scheme,
'host' => $host,
'port' => $port,
'path' => $path,
'query' => $query,
'fragment' => $fragment,
'digest' => $digest
];
return ($return) ? $array : $scheme . '://' . $host . $port . $path . $query . $fragment;
}
public function getServer($over = false)
{
if ($over) {
if ($this->config['PHPMAILER-domain'] !== '') {
return $this->config['PHPMAILER-domain'];
}
}
return isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : $_SERVER["SERVER_NAME"];
}
public function getServerPath($over = false)
{
if ($over) {
if ($this->config['PHPMAILER-domain'] !== '') {
return $this->config['PHPMAILER-domain'];
}
}
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
$protocol = "https://";
} elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') {
$protocol = "https://";
} else {
$protocol = "http://";
}
$domain = '';
if (isset($_SERVER['SERVER_NAME']) && strpos($_SERVER['SERVER_NAME'], '.') !== false) {
$domain = $_SERVER['SERVER_NAME'];
} elseif (isset($_SERVER['HTTP_HOST'])) {
if (strpos($_SERVER['HTTP_HOST'], ':') !== false) {
$domain = explode(':', $_SERVER['HTTP_HOST'])[0];
$port = explode(':', $_SERVER['HTTP_HOST'])[1];
if ($port !== "80" && $port !== "443") {
$domain = $_SERVER['HTTP_HOST'];
}
} else {
$domain = $_SERVER['HTTP_HOST'];
}
}
$path = str_replace("\\", "/", dirname($_SERVER['REQUEST_URI']));
$path = ($path !== '.') ? $path : '';
$url = $protocol . $domain . $path;
if (strpos($url, '/api') !== false) {
$url = explode('/api', $url);
return $url[0] . '/';
} else {
return $url;
}
}
public function convertIPToRange($ip)
{
$ip = trim($ip);
if (strpos($ip, '/') !== false) {
$explodeIP = explode('/', $ip);
$prefix = $explodeIP[1];
$start_ip = $explodeIP[0];
$explodeStart = explode('.', $start_ip);
if (count($explodeStart) == 4) {
$explodeStart[3] = $prefix == 32 ? $explodeStart[3] : 0;
$start_ip = implode('.', $explodeStart);
}
$ip_count = 1 << (32 - $prefix);
$start_ip_long = long2ip(ip2long($start_ip));
$last_ip_long = long2ip(ip2long($start_ip) + $ip_count - 1);
} elseif (substr_count($ip, '.') == 3) {
$start_ip_long = long2ip(ip2long($ip));
$last_ip_long = long2ip(ip2long($ip));
} else {
return false;
}
return [
'from' => $start_ip_long,
'to' => $last_ip_long
];
}
public function convertIPStringToRange($string = null)
{
$ips = [];
if ($string) {
$ipListing = explode(',', $string);
if (count($ipListing) > 0) {
foreach ($ipListing as $ip) {
$ips[] = $this->convertIPToRange($ip);
}
}
}
return $ips;
}
public function localIPRanges()
{
$mainArray = [
[
'from' => '10.0.0.0',
'to' => '10.255.255.255'
],
[
'from' => '172.16.0.0',
'to' => '172.31.255.255'
],
[
'from' => '192.168.0.0',
'to' => '192.168.255.255'
],
[
'from' => '127.0.0.1',
'to' => '127.255.255.255'
],
];
if (isset($this->config['localIPList'])) {
if ($this->config['localIPList'] !== '') {
$ipListing = explode(',', $this->config['localIPList']);
if (count($ipListing) > 0) {
foreach ($ipListing as $ip) {
$ipInfo = $this->convertIPToRange($ip);
if ($ipInfo) {
array_push($mainArray, $ipInfo);
}
}
}
}
}
/*
if ($this->config['localIPFrom']) {
$from = trim($this->config['localIPFrom']);
$override = true;
}
if ($this->config['localIPTo']) {
$to = trim($this->config['localIPTo']);
}
if ($override) {
$newArray = array(
'from' => $from,
'to' => (isset($to)) ? $to : $from
);
array_push($mainArray, $newArray);
}
*/
return $mainArray;
}
public function isLocal($checkIP = null)
{
$isLocal = false;
$userIP = ($checkIP) ? ip2long($checkIP) : ip2long($this->userIP());
$range = $this->localIPRanges();
foreach ($range as $ip) {
$low = ip2long($ip['from']);
$high = ip2long($ip['to']);
if ($userIP <= $high && $low <= $userIP) {
$isLocal = true;
}
}
return $isLocal;
}
public function isLocalOrServer()
{
$isLocalOrServer = false;
$isLocal = $this->isLocal();
if (!$isLocal) {
if ($this->userIP() == $this->serverIP()) {
$isLocalOrServer = true;
}
} else {
$isLocalOrServer = true;
}
return $isLocalOrServer;
}
public function human_filesize($bytes, $dec = 2)
{
$bytes = number_format($bytes, 0, '.', '');
$size = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$dec}f %s", $bytes / (1024 ** $factor), $size[$factor]);
}
public function apiResponseFormatter($response)
{
if (is_array($response)) {
return $response;
}
if (empty($response) || $response == '') {
return ['api_response' => 'No data'];
}
if ($this->json_validator($response)) {
return json_decode($response, true);
}
return ['api_response' => 'No data'];
}
public function json_validator($data = null)
{
if (!empty($data)) {
@json_decode($data);
return (json_last_error() === JSON_ERROR_NONE);
}
return false;
}
public function replace_first($search_str, $replacement_str, $src_str)
{
return (false !== ($pos = strpos($src_str, $search_str))) ? substr_replace($src_str, $replacement_str, $pos, strlen($search_str)) : $src_str;
}
/**
* Check if an array is a multidimensional array.
*
* @param array $arr The array to check
* @return boolean Whether the the array is a multidimensional array or not
*/
public function is_multi_array($x)
{
if (count(array_filter($x, 'is_array')) > 0) return true;
return false;
}
/**
* Convert an object to an array.
*
* @param array $object The object to convert
* @return array The converted array
*/
public function object_to_array($object)
{
if (!is_object($object) && !is_array($object)) return $object;
return array_map(array($this, 'object_to_array'), (array)$object);
}
/**
* Check if a value exists in the array/object.
*
* @param mixed $needle The value that you are searching for
* @param mixed $haystack The array/object to search
* @param boolean $strict Whether to use strict search or not
* @return boolean Whether the value was found or not
*/
public function search_for_value($needle, $haystack, $strict = true)
{
$haystack = $this->object_to_array($haystack);
if (is_array($haystack)) {
if ($this->is_multi_array($haystack)) { // Multidimensional array
foreach ($haystack as $subhaystack) {
if ($this->search_for_value($needle, $subhaystack, $strict)) {
return true;
}
}
} elseif (array_keys($haystack) !== range(0, count($haystack) - 1)) { // Associative array
foreach ($haystack as $key => $val) {
if ($needle == $val && !$strict) {
return true;
} elseif ($needle === $val && $strict) {
return true;
}
}
return false;
} else { // Normal array
if ($needle == $haystack && !$strict) {
return true;
} elseif ($needle === $haystack && $strict) {
return true;
}
}
}
return false;
}
public function makeDir($dirPath, $mode = 0777)
{
return is_dir($dirPath) || @mkdir($dirPath, $mode, true);
}
}
// Leave for deluge class
function getCert()
{
$url = 'http://curl.haxx.se/ca/cacert.pem';
$file = __DIR__ . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'cacert.pem';
$file2 = __DIR__ . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'cacert-initial.pem';
$useCert = (file_exists($file)) ? $file : $file2;
$context = stream_context_create(
array(
'ssl' => array(
'verify_peer' => true,
'cafile' => $useCert
)
)
);
if (!file_exists($file)) {
file_put_contents($file, fopen($url, 'r', false, $context));
} elseif (file_exists($file) && time() - 2592000 > filemtime($file)) {
file_put_contents($file, fopen($url, 'r', false, $context));
}
return $file;
}
// Leave for deluge class
function localURL($url, $force = false)
{
if ($force) {
return true;
}
if (strpos($url, 'https') !== false) {
preg_match("/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/", $url, $result);
$result = (!empty($result) ? true : false);
return $result;
}
return false;
}
// Maybe use later?
function curl($curl, $url, $headers = array(), $data = array())
{
// Initiate cURL
$curlReq = curl_init($url);
if (in_array(trim(strtoupper($curl)), ["GET", "POST", "PUT", "DELETE"])) {
curl_setopt($curlReq, CURLOPT_CUSTOMREQUEST, trim(strtoupper($curl)));
} else {
return null;
}
curl_setopt($curlReq, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlReq, CURLOPT_CAINFO, getCert());
curl_setopt($curlReq, CURLOPT_CONNECTTIMEOUT, 5);
if (localURL($url)) {
curl_setopt($curlReq, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($curlReq, CURLOPT_SSL_VERIFYPEER, 0);
}
// Format Headers
$cHeaders = array();
foreach ($headers as $k => $v) {
$cHeaders[] = $k . ': ' . $v;
}
if (count($cHeaders)) {
curl_setopt($curlReq, CURLOPT_HTTPHEADER, $cHeaders);
}
// Format Data
switch (isset($headers['Content-Type']) ? $headers['Content-Type'] : '') {
case 'application/json':
curl_setopt($curlReq, CURLOPT_POSTFIELDS, json_encode($data));
break;
case 'application/x-www-form-urlencoded':
curl_setopt($curlReq, CURLOPT_POSTFIELDS, http_build_query($data));
break;
default:
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
curl_setopt($curlReq, CURLOPT_POSTFIELDS, http_build_query($data));
}
// Execute
$result = curl_exec($curlReq);
$httpcode = curl_getinfo($curlReq);
// Close
curl_close($curlReq);
// Return
return array('content' => $result, 'http_code' => $httpcode);
}
// Maybe use later?
function getHeaders($url)
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
curl_setopt($ch, CURLOPT_CAINFO, getCert());
if (localURL($url)) {
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
}
curl_exec($ch);
$headers = curl_getinfo($ch);
curl_close($ch);
return $headers;
}
// Maybe use later?
function download($url, $path)
{
ini_set('max_execution_time', 0);
set_time_limit(0);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_CAINFO, getCert());
if (localURL($url)) {
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
}
$raw_file_data = curl_exec($ch);
curl_close($ch);
file_put_contents($path, $raw_file_data);
return (filesize($path) > 0) ? true : false;
}
// swagger
function getServerPath($over = false)
{
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
$protocol = "https://";
} elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') {
$protocol = "https://";
} else {
$protocol = "http://";
}
$domain = '';
if (isset($_SERVER['SERVER_NAME']) && strpos($_SERVER['SERVER_NAME'], '.') !== false) {
$domain = $_SERVER['SERVER_NAME'];
} elseif (isset($_SERVER['HTTP_HOST'])) {
if (strpos($_SERVER['HTTP_HOST'], ':') !== false) {
$domain = explode(':', $_SERVER['HTTP_HOST'])[0];
$port = explode(':', $_SERVER['HTTP_HOST'])[1];
if ($port !== "80" && $port !== "443") {
$domain = $_SERVER['HTTP_HOST'];
}
} else {
$domain = $_SERVER['HTTP_HOST'];
}
}
$url = $protocol . $domain . str_replace("\\", "/", dirname($_SERVER['REQUEST_URI']));
if (strpos($url, '/api') !== false) {
$url = explode('/api', $url);
return $url[0] . '/';
} else {
return $url;
}
}
// used for api return
function safe_json_encode($value, $options = 0, $depth = 512)
{
$encoded = json_encode($value, $options, $depth);
if ($encoded === false && $value && json_last_error() == JSON_ERROR_UTF8) {
$encoded = json_encode(utf8ize($value), $options, $depth);
}
return $encoded;
}
// used for api return
function utf8ize($mixed)
{
if (is_array($mixed)) {
foreach ($mixed as $key => $value) {
$mixed[$key] = utf8ize($value);
}
} elseif (is_string($mixed)) {
return mb_convert_encoding($mixed, "UTF-8", "UTF-8");
}
return $mixed;
}

View File

@@ -0,0 +1,105 @@
<?php
trait OAuthFunctions
{
public function traktOAuth()
{
$provider = new Bogstag\OAuth2\Client\Provider\Trakt(
[
'clientId' => $this->config['traktClientId'],
'clientSecret' => $this->config['traktClientSecret'],
'redirectUri' => $this->getServerPath() . 'api/v2/oauth/trakt'
],
[
'httpClient' => new GuzzleHttp\Client(['verify' => $this->getCert()]),
]
);
if (!isset($_GET['code'])) {
$authUrl = $provider->getAuthorizationUrl();
header('Location: ' . $authUrl);
exit;
} elseif (empty($_GET['state'])) {
exit('Invalid state');
} else {
try {
$token = $provider->getAccessToken('authorization_code', [
'code' => $_GET['code']
]);
$traktDetails = [
'traktAccessToken' => $token->getToken(),
'traktAccessTokenExpires' => gmdate('Y-m-d\TH:i:s\Z', $token->getExpires()),
'traktRefreshToken' => $token->getRefreshToken()
];
$this->updateConfig($traktDetails);
echo '
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="' . $this->getServerPath() . '/css/mvp.css">
<meta charset="utf-8">
<meta name="description" content="Trakt OAuth">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trakt OAuth</title>
</head>
<script language=javascript>
function closemyself() {
window.opener=self;
window.close();
}
</script>
<body onLoad="setTimeout(\'closemyself()\',3000);">
<main>
<section>
<aside>
<h3>Details Saved</h3>
<p><sup>(This window will close automatically)</sup></p>
</aside>
</section>
</main>
</body>
</html>
';
exit;
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
exit($e->getMessage());
}
}
}
public function traktOAuthRefresh()
{
$exp = $this->config['traktAccessTokenExpires'];
$exp = date('Y-m-d\TH:i:s\Z', strtotime($exp . ' - 30 days'));
if (time() - 2592000 > strtotime($exp)) {
$headers = [
'Content-Type' => 'application/json'
];
$data = [
'refresh_token' => $this->config['traktRefreshToken'],
'clientId' => $this->config['traktClientId'],
'clientSecret' => $this->config['traktClientSecret'],
'redirectUri' => $this->getServerPath() . 'api/v2/oauth/trakt',
'grant_type' => 'refresh_token'
];
$url = $this->qualifyURL('https://api.trakt.tv/oauth/token');
try {
$response = Requests::post($url, $headers, json_encode($data), []);
if ($response->success) {
$data = json_decode($response->body, true);
$newExp = date('Y-m-d\TH:i:s\Z', strtotime($this->currentTime . ' + 90 days'));
$traktDetails = [
'traktAccessToken' => $data['access_token'],
'traktAccessTokenExpires' => $newExp,
'traktRefreshToken' => $data['refresh_token']
];
$this->updateConfig($traktDetails);
return true;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Trakt')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,918 @@
<?php
trait OrganizrFunctions
{
public function docs($path): string
{
return 'https://organizr.gitbook.io/organizr/' . $path;
}
public function loadResources($files = [], $rootPath = '')
{
$scripts = '';
if (count($files) > 0) {
foreach ($files as $file) {
if (strtolower(pathinfo($file, PATHINFO_EXTENSION)) == 'js') {
$scripts .= $this->loadJavaResource($file, $rootPath);
} elseif (strtolower(pathinfo($file, PATHINFO_EXTENSION)) == 'css') {
$scripts .= $this->loadStyleResource($file, $rootPath);
}
}
}
return $scripts;
}
public function loadJavaResource($file = '', $rootPath = '')
{
return ($file !== '') ? '<script src="' . $rootPath . $file . '?v=' . trim($this->fileHash) . '"></script>' . "\n" : '';
}
public function loadStyleResource($file = '', $rootPath = '')
{
return ($file !== '') ? '<link href="' . $rootPath . $file . '?v=' . trim($this->fileHash) . '" rel="stylesheet">' . "\n" : '';
}
public function loadDefaultJavascriptFiles()
{
$javaFiles = [
'js/jquery-2.2.4.min.js',
'bootstrap/dist/js/bootstrap.min.js',
'plugins/bower_components/sidebar-nav/dist/sidebar-nav.min.js',
'js/jquery.slimscroll.js',
'plugins/bower_components/styleswitcher/jQuery.style.switcher.js',
'plugins/bower_components/moment/moment.js',
'plugins/bower_components/moment/moment-timezone.js',
'plugins/bower_components/jquery-wizard-master/dist/jquery-wizard.min.js',
'plugins/bower_components/jquery-wizard-master/libs/formvalidation/formValidation.min.js',
'plugins/bower_components/jquery-wizard-master/libs/formvalidation/bootstrap.min.js',
'js/bowser.min.js',
'js/jasny-bootstrap.js'
];
$scripts = '';
foreach ($javaFiles as $file) {
$scripts .= '<script src="' . $file . '?v=' . trim($this->fileHash) . '"></script>' . "\n";
}
return $scripts;
}
public function loadJavascriptFile($file)
{
return '<script>loadJavascript("' . $file . '?v=' . trim($this->fileHash) . '");' . "</script>\n";
}
public function embyJoinAPI($array)
{
$username = ($array['username']) ?? null;
$email = ($array['email']) ?? null;
$password = ($array['password']) ?? null;
if (!$username) {
$this->setAPIResponse('error', 'Username not supplied', 422);
return false;
}
if (!$email) {
$this->setAPIResponse('error', 'Email not supplied', 422);
return false;
}
if (!$password) {
$this->setAPIResponse('error', 'Password not supplied', 422);
return false;
}
return $this->embyJoin($username, $email, $password);
}
public function embyJoin($username, $email, $password)
{
try {
#create user in emby.
$headers = array(
"Accept" => "application/json"
);
$data = array();
$url = $this->config['embyURL'] . '/emby/Users/New?name=' . $username . '&api_key=' . $this->config['embyToken'];
$response = Requests::Post($url, $headers, json_encode($data), array());
$response = $response->body;
//return($response);
$response = json_decode($response, true);
//return($response);
$userID = $response["Id"];
//return($userID);
#authenticate as user to update password.
//randomizer four digits of DeviceId
// I dont think ther would be security problems with hardcoding deviceID but randomizing it would mitigate any issue.
$deviceIdSeceret = rand(0, 9) . "" . rand(0, 9) . "" . rand(0, 9) . "" . rand(0, 9);
//hardcoded device id with the first three digits random 0-9,0-9,0-9,0-9
$embyAuthHeader = 'MediaBrowser Client="Emby Mobile", Device="Firefox", DeviceId="' . $deviceIdSeceret . 'aWxssS81LgAggFdpbmRvd3MgTlQgMTAuMDsgV2luNjxx7IHf2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzcyLjAuMzYyNi4xMTkgU2FmYXJpLzUzNy4zNnwxNTUxNTczMTAyNDI4", Version="4.0.2.0"';
$headers = array(
"Accept" => "application/json",
"Content-Type" => "application/json",
"X-Emby-Authorization" => $embyAuthHeader
);
$data = array(
"Pw" => "",
"Username" => $username
);
$url = $this->config['embyURL'] . '/emby/Users/AuthenticateByName';
$response = Requests::Post($url, $headers, json_encode($data), array());
$response = $response->body;
$response = json_decode($response, true);
$userToken = $response["AccessToken"];
#update password
$embyAuthHeader = 'MediaBrowser Client="Emby Mobile", Device="Firefox", Token="' . $userToken . '", DeviceId="' . $deviceIdSeceret . 'aWxssS81LgAggFdpbmRvd3MgTlQgMTAuMDsgV2luNjxx7IHf2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzcyLjAuMzYyNi4xMTkgU2FmYXJpLzUzNy4zNnwxNTUxNTczMTAyNDI4", Version="4.0.2.0"';
$headers = array(
"Accept" => "application/json",
"Content-Type" => "application/json",
"X-Emby-Authorization" => $embyAuthHeader
);
$data = array(
"CurrentPw" => "",
"NewPw" => $password,
"Id" => $userID
);
$url = $this->config['embyURL'] . '/emby/Users/' . $userID . '/Password';
Requests::Post($url, $headers, json_encode($data), array());
#update config
$headers = array(
"Accept" => "application/json",
"Content-Type" => "application/json"
);
$url = $this->config['embyURL'] . '/emby/Users/' . $userID . '/Policy?api_key=' . $this->config['embyToken'];
$response = Requests::Post($url, $headers, $this->getEmbyTemplateUserJson(), array());
#add emby.media
try {
#seperate because this is not required
$headers = array(
"Accept" => "application/json",
"X-Emby-Authorization" => $embyAuthHeader
);
$data = array(
"ConnectUsername " => $email
);
$url = $this->config['embyURL'] . '/emby/Users/' . $userID . '/Connect/Link';
Requests::Post($url, $headers, json_encode($data), array());
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Emby')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
$this->setAPIResponse('success', 'User has joined Emby', 200);
return true;
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Emby')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
/*loads users from emby and returns a correctly formated policy for a new user.
*/
public function getEmbyTemplateUserJson()
{
$headers = array(
"Accept" => "application/json"
);
$data = array();
$url = $this->config['embyURL'] . '/emby/Users?api_key=' . $this->config['embyToken'];
$response = Requests::Get($url, $headers, array());
$response = $response->body;
$response = json_decode($response, true);
//$correct stores the template users object
$correct = null;
foreach ($response as $element) {
if ($element['Name'] == $this->config['INVITES-EmbyTemplate']) {
$correct = $element;
}
}
if ($correct == null) {
//return empty JSON if user incorrectly configured template
return "{}";
}
//select policy section and remove possibly dangerous rows.
$policy = $correct['Policy'];
unset($policy['AuthenticationProviderId']);
unset($policy['InvalidLoginAttemptCount']);
unset($policy['DisablePremiumFeatures']);
unset($policy['DisablePremiumFeatures']);
return (json_encode($policy));
}
public function checkHostPrefix($s)
{
if (empty($s)) {
return $s;
}
return (substr($s, -1, 1) == '\\') ? $s : $s . '\\';
}
public function approvedFileExtension($filename, $type = 'image')
{
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if ($type == 'image') {
switch ($ext) {
case 'gif':
case 'png':
case 'jpeg':
case 'jpg':
return true;
default:
return false;
}
} elseif ($type == 'cert') {
switch ($ext) {
case 'pem':
return true;
default:
return false;
}
}
}
public function approvedFileType($file, $type = 'image')
{
$finfo = new finfo(FILEINFO_MIME_TYPE);
$ext = $finfo->file($file);
if ($type == 'image') {
switch ($ext) {
case 'image/gif':
case 'image/png':
case 'image/jpeg':
case 'image/pjpeg':
return true;
default:
return false;
}
}
return false;
}
public function getImages()
{
$allIconsPrep = array();
$allIcons = array();
$ignore = array(".", "..", "._.DS_Store", ".DS_Store", ".pydio_id", "index.html");
$dirname = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'tabs' . DIRECTORY_SEPARATOR;
$path = 'plugins/images/tabs/';
$images = scandir($dirname);
foreach ($images as $image) {
if (!in_array($image, $ignore)) {
$allIconsPrep[$image] = array(
'path' => $path,
'name' => $image
);
}
}
$dirname = $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'userTabs' . DIRECTORY_SEPARATOR;
$path = 'data/userTabs/';
$images = scandir($dirname);
foreach ($images as $image) {
if (!in_array($image, $ignore)) {
$allIconsPrep[$image] = array(
'path' => $path,
'name' => $image
);
}
}
ksort($allIconsPrep);
foreach ($allIconsPrep as $item) {
$allIcons[] = $item['path'] . $item['name'];
}
return $allIcons;
}
public function imageSelect($form)
{
$i = 1;
$images = $this->getImages();
$return = '<select class="form-control tabIconImageList" id="' . $form . '-chooseImage" name="chooseImage"><option lang="en">Select or type Icon</option>';
foreach ($images as $image) {
$i++;
$return .= '<option value="' . $image . '">' . basename($image) . '</option>';
}
return $return . '</select>';
}
public function getThemes()
{
$themes = array();
foreach (glob(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . "*.css") as $filename) {
$themes[] = array(
'name' => preg_replace('/\\.[^.\\s]{3,4}$/', '', basename($filename)),
'value' => preg_replace('/\\.[^.\\s]{3,4}$/', '', basename($filename))
);
}
return $themes;
}
public function getSounds()
{
$sounds = array();
foreach (glob(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'sounds' . DIRECTORY_SEPARATOR . 'default' . DIRECTORY_SEPARATOR . "*.mp3") as $filename) {
$sounds[] = array(
'name' => preg_replace('/\\.[^.\\s]{3,4}$/', '', basename($filename)),
'value' => preg_replace('/\\.[^.\\s]{3,4}$/', '', 'plugins/sounds/default/' . basename($filename) . '.mp3')
);
}
foreach (glob(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'sounds' . DIRECTORY_SEPARATOR . 'custom' . DIRECTORY_SEPARATOR . "*.mp3") as $filename) {
$sounds[] = array(
'name' => preg_replace('/\\.[^.\\s]{3,4}$/', '', basename($filename)),
'value' => preg_replace('/\\.[^.\\s]{3,4}$/', '', 'plugins/sounds/custom/' . basename($filename) . '.mp3')
);
}
return $sounds;
}
public function getBranches()
{
return array(
array(
'name' => 'Develop',
'value' => 'v2-develop'
),
array(
'name' => 'Master',
'value' => 'v2-master'
)
);
}
public function getSettingsTabs()
{
return array(
array(
'name' => 'Tab Editor',
'value' => '0'
),
array(
'name' => 'Customize',
'value' => '1'
),
array(
'name' => 'User Management',
'value' => '2'
),
array(
'name' => 'Image Manager',
'value' => '3'
),
array(
'name' => 'Plugins',
'value' => '4'
),
array(
'name' => 'System Settings',
'value' => '5'
)
);
}
public function getAuthTypes()
{
return array(
array(
'name' => 'Organizr DB',
'value' => 'internal'
),
array(
'name' => 'Organizr DB + Backend',
'value' => 'both'
),
array(
'name' => 'Backend Only',
'value' => 'external'
)
);
}
public function getLDAPOptions()
{
return array(
array(
'name' => 'Active Directory',
'value' => '1'
),
array(
'name' => 'OpenLDAP',
'value' => '2'
),
array(
'name' => 'Free IPA',
'value' => '3'
),
);
}
public function getAuthBackends()
{
$backendOptions = array();
$backendOptions[] = array(
'name' => 'Choose Backend',
'value' => false,
'disabled' => true
);
foreach (array_filter(get_class_methods('Organizr'), function ($v) {
return strpos($v, 'plugin_auth_') === 0;
}) as $value) {
$name = str_replace('plugin_auth_', '', $value);
if ($name == 'ldap') {
if (!function_exists('ldap_connect')) {
continue;
}
}
if ($name == 'ldap_disabled') {
if (function_exists('ldap_connect')) {
continue;
}
}
if (strpos($name, 'disabled') === false) {
$backendOptions[] = array(
'name' => ucwords(str_replace('_', ' ', $name)),
'value' => $name
);
} else {
$backendOptions[] = array(
'name' => $this->$value(),
'value' => 'none',
'disabled' => true,
);
}
}
ksort($backendOptions);
return $backendOptions;
}
public function importUserButtons()
{
$emptyButtons = '
<div class="col-md-12">
<div class="white-box bg-org">
<h3 class="box-title m-0" lang="en">Currently User import is available for Plex only.</h3> </div>
</div>
';
$buttons = '';
if (!empty($this->config['plexToken'])) {
$buttons .= '<button class="btn m-b-20 m-r-20 bg-plex text-muted waves-effect waves-light importUsersButton" onclick="importUsers(\'plex\')" type="button"><span class="btn-label"><i class="mdi mdi-plex"></i></span><span lang="en">Import Plex Users</span></button>';
}
if (!empty($this->config['jellyfinURL']) && !empty($this->config['jellyfinToken'])) {
$buttons .= '<button class="btn m-b-20 m-r-20 bg-primary text-muted waves-effect waves-light importUsersButton" onclick="importUsers(\'jellyfin\')" type="button"><span class="btn-label"><i class="mdi mdi-fish"></i></span><span lang="en">Import Jellyfin Users</span></button>';
}
if (!empty($this->config['embyURL']) && !empty($this->config['embyToken'])) {
$buttons .= '<button class="btn m-b-20 m-r-20 bg-emby text-muted waves-effect waves-light importUsersButton" onclick="importUsers(\'emby\')" type="button"><span class="btn-label"><i class="mdi mdi-emby"></i></span><span lang="en">Import Emby Users</span></button>';
}
return ($buttons !== '') ? $buttons : $emptyButtons;
}
public function getHomepageMediaImage()
{
$refresh = false;
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
if (!file_exists($cacheDirectory)) {
mkdir($cacheDirectory, 0777, true);
}
@$image_url = $_GET['img'];
@$key = $_GET['key'];
@$image_height = $_GET['height'];
@$image_width = $_GET['width'];
@$source = $_GET['source'];
@$itemType = $_GET['type'];
if (strpos($key, '$') !== false) {
$key = explode('$', $key)[0];
$refresh = true;
}
switch ($source) {
case 'plex':
$plexAddress = $this->qualifyURL($this->config['plexURL']);
$image_src = $plexAddress . '/photo/:/transcode?height=' . $image_height . '&width=' . $image_width . '&upscale=1&url=' . $image_url . '&X-Plex-Token=' . $this->config['plexToken'];
break;
case 'emby':
$embyAddress = $this->qualifyURL($this->config['embyURL']);
$imgParams = array();
if (isset($_GET['height'])) {
$imgParams['height'] = 'maxHeight=' . $_GET['height'];
}
if (isset($_GET['width'])) {
$imgParams['width'] = 'maxWidth=' . $_GET['width'];
}
$image_src = $embyAddress . '/Items/' . $image_url . '/Images/' . $itemType . '?' . implode('&', $imgParams);
break;
case 'jellyfin':
$jellyfinAddress = $this->qualifyURL($this->config['jellyfinURL']);
$imgParams = array();
if (isset($_GET['height'])) {
$imgParams['height'] = 'maxHeight=' . $_GET['height'];
}
if (isset($_GET['width'])) {
$imgParams['width'] = 'maxWidth=' . $_GET['width'];
}
$image_src = $jellyfinAddress . '/Items/' . $image_url . '/Images/' . $itemType . '?' . implode('&', $imgParams);
break;
default:
# code...
break;
}
if (strpos($key, '-') !== false) {
$noImage = 'no-' . explode('-', $key)[1] . '.png';
} else {
$noImage = 'no-np.png';
}
$noImage = $this->root . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'homepage' . DIRECTORY_SEPARATOR . $noImage;
if (isset($image_url) && isset($image_height) && isset($image_width) && isset($image_src)) {
$cachefile = $cacheDirectory . $key . '.jpg';
$cachetime = 604800;
// Serve from the cache if it is younger than $cachetime
if (file_exists($cachefile) && (time() - $cachetime < filemtime($cachefile)) && $refresh == false) {
header('Content-type: image/jpeg');
if (filesize($cachefile) > 0) {
@readfile($cachefile);
} else {
@readfile($noImage);
}
exit;
}
$options = array('verify' => false);
$response = Requests::get($image_src, array(), $options);
if ($response->success) {
ob_start(); // Start the output buffer
header('Content-type: image/jpeg');
echo $response->body;
// Cache the output to a file
$fp = fopen($cachefile, 'wb');
fwrite($fp, ob_get_contents());
fclose($fp);
ob_end_flush(); // Send the output to the browser
die();
} else {
header('Content-type: image/jpeg');
@readfile($noImage);
}
} else {
header('Content-type: image/jpeg');
@readfile($noImage);
}
}
public function cacheImage($url, $name, $extension = 'jpg')
{
$cacheDirectory = $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
if (!file_exists($cacheDirectory)) {
mkdir($cacheDirectory, 0777, true);
}
$cacheFile = $cacheDirectory . $name . '.' . $extension;
$cacheTime = 604800;
$ctx = stream_context_create(array(
'http' => array(
'timeout' => 5,
'protocol_version' => 1.1,
'header' => 'Connection: close'
)
));
if ((file_exists($cacheFile) && (time() - $cacheTime) > filemtime($cacheFile)) || !file_exists($cacheFile)) {
@copy($url, $cacheFile, $ctx);
}
}
public function checkFrame($array, $url)
{
if (array_key_exists("x-frame-options", $array)) {
if (gettype($array['x-frame-options']) == 'array') {
$array['x-frame-options'] = $array['x-frame-options'][0];
}
$array['x-frame-options'] = strtolower($array['x-frame-options']);
if ($array['x-frame-options'] == "deny") {
return false;
} elseif ($array['x-frame-options'] == "sameorgin") {
$digest = parse_url($url);
$host = ($digest['host'] ?? '');
if ($this->getServer() == $host) {
return true;
} else {
return false;
}
} elseif (strpos($array['x-frame-options'], 'allow-from') !== false) {
$explodeServers = explode(' ', $array['x-frame-options']);
$allowed = false;
foreach ($explodeServers as $server) {
$digest = parse_url($server);
$host = ($digest['host'] ?? '');
if ($this->getServer() == $host) {
$allowed = true;
}
}
return $allowed;
} else {
return false;
}
} else {
if (!$array) {
return false;
}
return true;
}
}
public function frameTest($url)
{
if (!$url || $url == '') {
$this->setAPIResponse('error', 'URL not supplied', 404);
return false;
}
$array = array_change_key_case(get_headers($this->qualifyURL($url), 1));
$url = $this->qualifyURL($url);
if ($this->checkFrame($array, $url)) {
$this->setAPIResponse('success', 'URL approved for iFrame', 200);
return true;
} else {
$this->setAPIResponse('error', 'URL failed approval for iFrame', 409);
return false;
}
}
public function groupSelect()
{
$groups = $this->getAllGroups();
$select = array();
foreach ($groups as $key => $value) {
$select[] = array(
'name' => $value['group'],
'value' => $value['group_id']
);
}
return $select;
}
public function userSelect()
{
$users = $this->getAllUsers();
$select = [];
foreach ($users as $key => $value) {
$select[] = array(
'name' => $value['username'],
'value' => $value['id']
);
}
return $select;
}
public function showLogin()
{
if ($this->config['hideRegistration'] == false) {
return '<p><span lang="en">Don\'t have an account?</span><a href="#" class="text-primary m-l-5 to-register"><b lang="en">Sign Up</b></a></p>';
}
}
public function checkoAuth()
{
return $this->config['plexoAuth'] && $this->config['authBackend'] == 'plex' && $this->config['authType'] !== 'internal';
}
public function checkoAuthOnly()
{
return $this->config['plexoAuth'] && $this->config['authBackend'] == 'plex' && $this->config['authType'] == 'external';
}
public function showoAuth()
{
$buttons = '';
if ($this->config['plexoAuth'] && $this->config['authBackend'] == 'plex' && $this->config['authType'] !== 'internal') {
$buttons .= '<a href="javascript:void(0)" onclick="oAuthStart(\'plex\')" class="btn btn-lg btn-block text-uppercase waves-effect waves-light bg-plex text-muted" data-toggle="tooltip" title="" data-original-title="Login with Plex"> <span>Login</span><i aria-hidden="true" class="mdi mdi-plex m-l-5"></i> </a>';
}
return ($buttons) ? '
<div class="panel">
<div class="panel-heading bg-org" id="plex-login-heading" role="tab">
<a class="panel-title" data-toggle="collapse" href="#plex-login-collapse" data-parent="#login-panels" aria-expanded="false" aria-controls="organizr-login-collapse">
<img class="lazyload loginTitle" data-src="plugins/images/tabs/plex.png"> &nbsp;
<span class="text-uppercase fw300" lang="en">Login with Plex</span>
</a>
</div>
<div class="panel-collapse collapse in" id="plex-login-collapse" aria-labelledby="plex-login-heading" role="tabpanel">
<div class="panel-body">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 text-center">
<div class="social m-b-0">' . $buttons . '</div>
</div>
</div>
</div>
</div>
</div>
' : '';
}
public function logoOrText()
{
$showLogo = $this->config['minimalLoginScreen'] ? '' : 'visible-xs';
if ($this->config['useLogoLogin'] == false) {
$html = '<h1>' . $this->config['title'] . '</h1>';
} else {
$html = '<img class="loginLogo" src="' . $this->config['loginLogo'] . '" alt="Home" />';
}
return '<a href="javascript:void(0)" class="text-center db ' . $showLogo . '" id="login-logo">' . $html . '</a>';
}
public function settingsDocker()
{
$type = ($this->docker) ? 'Official Docker' : 'Native';
return '<li><div class="bg-info"><i class="mdi mdi-flag mdi-24px text-white"></i></div><span class="text-muted hidden-xs m-t-10" lang="en">Install Type</span> ' . $type . '</li>';
}
public function settingsPathChecks()
{
$paths = $this->pathsWritable($this->paths);
$items = '';
$type = (array_search(false, $paths)) ? 'Not Writable' : 'Writable';
$result = '<li class="mouse" onclick="toggleWritableFolders();"><div class="bg-info"><i class="mdi mdi-folder mdi-24px text-white"></i></div><span class="text-muted hidden-xs m-t-10" lang="en">Organizr Paths</span> ' . $type . '</li>';
foreach ($paths as $k => $v) {
$items .= '<li class="folders-writable hidden"><div class="bg-primary"><i class="mdi mdi-folder mdi-24px text-white"></i></div><a tabindex="0" type="button" class="btn btn-default btn-outline popover-info pull-right clipboard" lang="en" data-container="body" title="" data-toggle="popover" data-placement="left" data-content="' . $v['path'] . '" data-original-title="File Path" data-clipboard-text="' . $v['path'] . '">' . $k . '</a> ' . (($v['writable']) ? 'Writable' : 'Not Writable') . '</li>';
}
return $result . $items;
}
public function pathsWritable($paths)
{
$results = array();
foreach ($paths as $k => $v) {
$results[$k] = [
'writable' => is_writable($v),
'path' => $v
];
}
return $results;
}
public function clearTautulliTokens()
{
foreach (array_keys($_COOKIE) as $k => $v) {
if (strpos($v, 'tautulli') !== false) {
$this->coookie('delete', $v);
}
}
}
public function clearJellyfinTokens()
{
foreach (array_keys($_COOKIE) as $k => $v) {
if (strpos($v, 'user-') !== false) {
$this->coookie('delete', $v);
}
}
$this->coookie('delete', 'jellyfin_credentials');
}
public function clearKomgaToken()
{
if (isset($_COOKIE['komga_token'])) {
try {
$url = $this->qualifyURL($this->config['komgaURL']);
$options = $this->requestOptions($url, 60000, true, false);
$response = Requests::post($url . '/api/v1/users/logout', ['X-Auth-Token' => $_COOKIE['komga_token']], $options);
if ($response->success) {
$this->setLoggerChannel('Komga')->info('Logged User out');
} else {
$this->setLoggerChannel('Komga')->warning('Unable to Logged User out');
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Komga')->error($e);
}
$this->coookie('delete', 'komga_token');
}
}
public function analyzeIP($ip)
{
if (strpos($ip, '/') !== false) {
$explodeIP = explode('/', $ip);
$prefix = $explodeIP[1];
$start_ip = $explodeIP[0];
$ip_count = 1 << (32 - $prefix);
$start_ip_long = ip2long($start_ip);
$last_ip_long = ip2long($start_ip) + $ip_count - 1;
} elseif (substr_count($ip, '.') == 3) {
$start_ip_long = ip2long($ip);
$last_ip_long = ip2long($ip);
}
return (isset($start_ip_long) && isset($last_ip_long)) ? array('from' => $start_ip_long, 'to' => $last_ip_long) : false;
}
public function authProxyRangeCheck($from, $to)
{
$approved = false;
$userIP = ip2long($_SERVER['REMOTE_ADDR']);
$low = $from;
$high = $to;
if ($userIP <= $high && $low <= $userIP) {
$approved = true;
}
$this->logger->debug('authProxy range check', ['server_ip' => ['long' => $userIP, 'short' => long2ip($userIP)], 'range_from' => ['long' => $from, 'short' => long2ip($from)], 'range_to' => ['long' => $to, 'short' => long2ip($to)], 'approved' => $approved]);
return $approved;
}
public function userDefinedIdReplacementLink($link, $variables)
{
if (!isset($link) || $link == '') {
return null;
}
return strtr($link, $variables);
}
public function requestOptions($url, $timeout = null, $override = false, $customCertificate = false, $extras = null)
{
$options = [];
if (is_numeric($timeout)) {
if ($timeout >= 1000) {
$timeout = $timeout / 1000;
}
$options = array_merge($options, array('timeout' => $timeout));
}
if ($customCertificate) {
if ($this->hasCustomCert()) {
$options = array_merge($options, array('verify' => $this->getCustomCert(), 'verifyname' => false));
}
}
if ($this->localURL($url, $override)) {
$options = array_merge($options, array('verify' => false, 'verifyname' => false));
}
if ($extras) {
if (gettype($extras) == 'array') {
$options = array_merge($options, $extras);
}
}
return array_merge($options, array('useragent' => 'organizr/' . $this->version, 'connect_timeout' => 5));
}
public function showHTML(string $title = 'Organizr Alert', string $notice = '', bool $autoClose = false)
{
$close = $autoClose ? 'onLoad="setTimeout(\'closemyself()\',3000);"' : '';
$closeMessage = $autoClose ? '<p><sup>(This window will close automatically)</sup></p>' : '';
return
'<!DOCTYPE html>
<html lang="en">
<head>
<style>' . file_get_contents($this->root . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'mvp.css') . '</style>
<meta charset="utf-8">
<meta name="description" content="' . $title . '">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>' . $title . '</title>
</head>
<script language=javascript>
function closemyself() {
window.opener=self;
window.close();
}
</script>
<body ' . $close . '>
<main>
<section>
<div>
<h3>' . $title . '</h3>
<p>' . $notice . '</p>
' . $closeMessage . '
</div>
</section>
</main>
</body>
</html>';
}
public function buildSettingsMenus($menuItems, $menuName)
{
$selectMenuItems = '';
$unorderedListMenuItems = '';
$menuNameLower = strtolower(str_replace(' ', '-', $menuName));
foreach ($menuItems as $menuItem) {
$anchorShort = str_replace('-anchor', '', $menuItem['anchor']);
$active = ($menuItem['active']) ? 'active' : '';
$apiPage = ($menuItem['api']) ? 'loadSettingsPage2(\'' . $menuItem['api'] . '\',\'#' . $anchorShort . '\',\'' . $menuItem['name'] . '\');' : '';
$onClick = (isset($menuItem['onclick'])) ? $menuItem['onclick'] : '';
$selectMenuItems .= '<option value="#' . $menuItem['anchor'] . '" lang="en">' . $menuItem['name'] . '</option>';
$unorderedListMenuItems .= '
<li onclick="changeSettingsMenu(\'Settings::' . $menuName . '::' . $menuItem['name'] . '\'); ' . $apiPage . $onClick . '" role="presentation" class="' . $active . '">
<a id="' . $menuItem['anchor'] . '" href="#' . $anchorShort . '" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="true">
<span lang="en">' . $menuItem['name'] . '</span>
</a>
</li>';
}
$selectMenu = '<select class="form-control settings-dropdown-box ' . $menuNameLower . '-menu w-100 visible-xs">' . $selectMenuItems . '</select>';
$unorderedListMenu = '<ul class="nav customtab2 nav-tabs nav-non-mobile hidden-xs" data-dropdown="' . $menuNameLower . '-menu" role="tablist">' . $unorderedListMenuItems . '</ul>';
return $selectMenu . $unorderedListMenu;
}
public function isJSON($string)
{
return is_string($string) && is_array(json_decode($string, true)) && (json_last_error() == JSON_ERROR_NONE);
}
public function isXML($string)
{
libxml_use_internal_errors(true);
return (bool)simplexml_load_string($string);
}
public function testAndFormatString($string)
{
if ($this->isJSON($string)) {
return ['type' => 'json', 'data' => json_decode($string, true)];
} elseif ($this->isXML($string)) {
libxml_use_internal_errors(true);
return ['type' => 'xml', 'data' => simplexml_load_string($string)];
} else {
return ['type' => 'string', 'data' => $string];
}
}
}

View File

@@ -0,0 +1,2 @@
<?php
/* Depreciated */

View File

@@ -0,0 +1,6 @@
<?php
trait PluginFunctions
{
}

View File

@@ -0,0 +1,398 @@
<?php
trait SSOFunctions
{
public function ssoCookies()
{
$cookies = array(
'myPlexAccessToken' => $_COOKIE['mpt'] ?? false,
'id_token' => $_COOKIE['Auth'] ?? false,
'jellyfin_credentials' => $_COOKIE['jellyfin_credentials'] ?? false,
'komga_token' => $_COOKIE['komga_token'] ?? false
);
// Jellyfin cookie
foreach (array_keys($_COOKIE) as $k => $v) {
if (strpos($v, 'user-') !== false) {
$cookiesToAdd = [
$v => $_COOKIE[$v]
];
$cookies = array_merge($cookies, $cookiesToAdd);
}
}
return $cookies;
}
public function getSSOList($enabledOnly = false)
{
$searchTerm = 'sso';
$list = array_filter($this->config, function ($k) use ($searchTerm) {
return stripos($k, $searchTerm) !== false;
}, ARRAY_FILTER_USE_KEY);
foreach ($list as $key => $sso) {
if (stripos(substr($key, 0, 3), $searchTerm) === false) {
unset($list[$key]);
continue;
}
if (gettype($sso) !== 'boolean') {
unset($list[$key]);
}
if ($enabledOnly) {
if (!$sso) {
unset($list[$key]);
}
}
}
return $list;
}
public function getSSOEnabledCount()
{
$count = count($this->getSSOList(true));
return ($count <= 0) ? 1 : $count;
}
public function getSSOTimeout()
{
return (60000 / $this->getSSOEnabledCount());
}
public function getSSOUserFor($app, $userobj)
{
$map = array(
'jellyfin' => 'username',
'ombi' => 'username',
'overseerr' => 'email',
'tautulli' => 'username',
'petio' => 'username',
'komga' => 'email'
);
return (gettype($userobj) == 'string') ? $userobj : $userobj[$map[$app]];
}
public function ssoCheck($userobj, $password, $token = null)
{
$this->setCurrentUser(false);
$this->setLoggerChannel('Authentication', $this->user['username']);
$this->logger->debug('Starting SSO check function');
if ($this->config['ssoPlex'] && $token) {
$this->logger->debug('Setting Plex SSO cookie');
$this->coookie('set', 'mpt', $token, $this->config['rememberMeDays'], false);
}
if ($this->config['ssoOmbi']) {
$this->logger->debug('Starting Ombi SSO check function');
$fallback = ($this->config['ombiFallbackUser'] !== '' && $this->config['ombiFallbackPassword'] !== '');
$ombiToken = $this->getOmbiToken($this->getSSOUserFor('ombi', $userobj), $password, $token, $fallback);
if ($ombiToken) {
$this->logger->debug('Setting Ombi SSO cookie');
$this->coookie('set', 'Auth', $ombiToken, $this->config['rememberMeDays'], false);
} else {
$this->logger->debug('No Ombi token received from backend');
}
}
if ($this->config['ssoTautulli'] && $this->qualifyRequest($this->config['ssoTautulliAuth'])) {
$this->logger->debug('Starting Tautulli SSO check function');
$tautulliToken = $this->getTautulliToken($this->getSSOUserFor('tautulli', $userobj), $password, $token);
if ($tautulliToken) {
foreach ($tautulliToken as $key => $value) {
$this->logger->debug('Setting Tautulli SSO cookie');
$this->coookie('set', 'tautulli_token_' . $value['uuid'], $value['token'], $this->config['rememberMeDays'], true, $value['path']);
}
} else {
$this->logger->debug('No Tautulli token received from backend');
}
}
if ($this->config['ssoJellyfin']) {
$this->logger->debug('Starting Jellyfin SSO check function');
$jellyfinToken = $this->getJellyfinToken($this->getSSOUserFor('jellyfin', $userobj), $password);
if ($jellyfinToken) {
foreach ($jellyfinToken as $k => $v) {
$this->logger->debug('Setting Jellyfin SSO cookie');
$this->coookie('set', $k, $v, $this->config['rememberMeDays'], false);
}
} else {
$this->logger->debug('No Jellyfin token received from backend');
}
}
if ($this->config['ssoOverseerr']) {
$this->logger->debug('Starting Overseerr SSO check function');
$fallback = ($this->config['overseerrFallbackUser'] !== '' && $this->config['overseerrFallbackPassword'] !== '');
$overseerrToken = $this->getOverseerrToken($this->getSSOUserFor('overseerr', $userobj), $password, $token, $fallback);
if ($overseerrToken) {
$this->logger->debug('Setting Overseerr SSO cookie');
$this->coookie('set', 'connect.sid', $overseerrToken, $this->config['rememberMeDays'], false);
} else {
$this->logger->debug('No Overseerr token received from backend');
}
}
if ($this->config['ssoPetio']) {
$this->logger->debug('Starting Petio SSO check function');
$fallback = ($this->config['petioFallbackUser'] !== '' && $this->config['petioFallbackPassword'] !== '');
$petioToken = $this->getPetioToken($this->getSSOUserFor('petio', $userobj), $password, $token, $fallback);
if ($petioToken) {
$this->logger->debug('Setting Petio SSO cookie');
$this->coookie('set', 'petio_jwt', $petioToken, $this->config['rememberMeDays'], false);
} else {
$this->logger->debug('No Petio token received from backend');
}
}
if ($this->config['ssoKomga'] && $this->qualifyRequest($this->config['ssoKomgaAuth'])) {
$this->logger->debug('Starting Komga SSO check function');
$fallback = ($this->config['komgaFallbackUser'] !== '' && $this->config['komgaFallbackPassword'] !== '');
$komga = $this->getKomgaToken($this->getSSOUserFor('komga', $userobj), $password, $fallback);
if ($komga) {
$this->logger->debug('Setting Komga SSO cookie');
$this->coookie('set', 'komga_token', $komga, $this->config['rememberMeDays'], false);
} else {
$this->logger->debug('No Komga token received from backend');
}
}
return true;
}
public function getKomgaToken($email, $password, $fallback = false)
{
$token = null;
$useMaster = false;
try {
if ($password) {
if ($password == '') {
$useMaster = true;
}
} else {
$useMaster = true;
}
if ($useMaster) {
if ($this->config['komgaSSOMasterPassword'] !== '') {
$password = $this->decrypt($this->config['komgaSSOMasterPassword']);
}
}
$credentials = array('auth' => new Requests_Auth_Digest(array($email, $password)));
$url = $this->qualifyURL($this->config['komgaURL']);
$options = $this->requestOptions($url, $this->getSSOTimeout(), true, false, $credentials);
$response = Requests::get($url . '/api/v1/users/me', ['X-Auth-Token' => 'organizrSSO'], $options);
if ($response->success) {
if ($response->headers['x-auth-token']) {
$this->setLoggerChannel('Komga')->info('Grabbed token');
$token = $response->headers['x-auth-token'];
} else {
$this->setLoggerChannel('Komga')->warning('Komga did not return Token');
}
} else {
if ($fallback) {
$this->setLoggerChannel('Komga')->warning('Komga did not return Token - Will retry using fallback credentials');
} else {
$this->setLoggerChannel('Komga')->warning('Komga did not return Token');
}
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Komga')->error($e);
}
if ($token) {
return $token;
} elseif ($fallback) {
return $this->getKomgaToken($this->config['komgaFallbackUser'], $this->decrypt($this->config['komgaFallbackPassword']), false);
} else {
return false;
}
}
public function getJellyfinToken($username, $password)
{
$token = null;
try {
$url = $this->qualifyURL($this->config['jellyfinURL']);
$ssoUrl = $this->qualifyURL($this->config['jellyfinSSOURL']);
$headers = array(
'X-Emby-Authorization' => 'MediaBrowser Client="Organizr Jellyfin Tab", Device="Organizr_PHP", DeviceId="Organizr_SSO", Version="1.0"',
"Accept" => "application/json",
"Content-Type" => "application/json",
"X-Forwarded-For" => $this->userIP()
);
$data = array(
"Username" => $username,
"Pw" => $password
);
$endpoint = '/Users/authenticatebyname';
$options = $this->requestOptions($url, $this->getSSOTimeout());
$response = Requests::post($url . $endpoint, $headers, json_encode($data), $options);
if ($response->success) {
$token = json_decode($response->body, true);
$this->setLoggerChannel('JellyFin')->info('Grabbed token');
$key = 'user-' . $token['User']['Id'] . '-' . $token['ServerId'];
$jellyfin[$key] = json_encode($token['User']);
$jellyfin['jellyfin_credentials'] = '{"Servers":[{"ManualAddress":"' . $ssoUrl . '","Id":"' . $token['ServerId'] . '","UserId":"' . $token['User']['Id'] . '","AccessToken":"' . $token['AccessToken'] . '"}]}';
return $jellyfin;
} else {
$this->setLoggerChannel('JellyFin')->warning('JellyFin did not return Token');
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Jellyfin')->error($e);
}
return false;
}
public function getOmbiToken($username, $password, $oAuthToken = null, $fallback = false)
{
$token = null;
try {
$url = $this->qualifyURL($this->config['ombiURL']);
$headers = array(
"Accept" => "application/json",
"Content-Type" => "application/json",
"X-Forwarded-For" => $this->userIP()
);
$data = array(
"username" => ($oAuthToken ? "" : $username),
"password" => ($oAuthToken ? "" : $password),
"rememberMe" => "true",
"plexToken" => $oAuthToken
);
$endpoint = ($oAuthToken) ? '/api/v1/Token/plextoken' : '/api/v1/Token';
$options = $this->requestOptions($url, $this->getSSOTimeout());
$response = Requests::post($url . $endpoint, $headers, json_encode($data), $options);
if ($response->success) {
$token = json_decode($response->body, true)['access_token'];
$this->setLoggerChannel('Ombi')->info('Grabbed token');
} else {
if ($fallback) {
$this->setLoggerChannel('Ombi')->warning('Ombi did not return Token - Will retry using fallback credentials');
} else {
$this->setLoggerChannel('Ombi')->warning('Ombi did not return Token');
}
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Ombi')->error($e);
}
if ($token) {
return $token;
} elseif ($fallback) {
return $this->getOmbiToken($this->config['ombiFallbackUser'], $this->decrypt($this->config['ombiFallbackPassword']), null, false);
} else {
return false;
}
}
public function getTautulliToken($username, $password, $plexToken = null)
{
$token = null;
$tautulliURLList = explode(',', $this->config['tautulliURL']);
if (count($tautulliURLList) !== 0) {
foreach ($tautulliURLList as $key => $value) {
try {
$url = $this->qualifyURL($value);
$headers = array(
"Accept" => "application/json",
"Content-Type" => "application/x-www-form-urlencoded",
"User-Agent" => $_SERVER ['HTTP_USER_AGENT'] ?? null,
"X-Forwarded-For" => $this->userIP()
);
$data = array(
"username" => ($plexToken ? "" : $username),
"password" => ($plexToken ? "" : $password),
"token" => $plexToken,
"remember_me" => 1,
);
$options = $this->requestOptions($url, $this->getSSOTimeout());
$response = Requests::post($url . '/auth/signin', $headers, $data, $options);
if ($response->success) {
$qualifiedURL = $this->qualifyURL($url, true);
$path = ($qualifiedURL['path']) ? $qualifiedURL['path'] : '/';
$token[$key]['token'] = json_decode($response->body, true)['token'];
$token[$key]['uuid'] = json_decode($response->body, true)['uuid'];
$token[$key]['path'] = $path;
$this->setLoggerChannel('Tautulli')->info('Grabbed token from: ' . $url);
} else {
$this->setLoggerChannel('Tautulli')->warning('Error on URL: ' . $url);
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Tautulli')->error($e);
}
}
}
return ($token) ? $token : false;
}
public function getOverseerrToken($email, $password, $oAuthToken = null, $fallback = false)
{
$token = null;
try {
$url = $this->qualifyURL($this->config['overseerrURL']);
$headers = array(
"Content-Type" => "application/json",
"X-Forwarded-For" => $this->userIP()
);
$data = array(
"email" => ($oAuthToken ? "" : $email), // not needed yet
"password" => ($oAuthToken ? "" : $password), // not needed yet
"authToken" => $oAuthToken
);
$endpoint = ($oAuthToken ? '/api/v1/auth/plex' : '/api/v1/auth/local');
$options = $this->requestOptions($url, $this->getSSOTimeout());
$response = Requests::post($url . $endpoint, $headers, json_encode($data), $options);
if ($response->success) {
$user = json_decode($response->body, true); // not really needed yet
$token = $response->cookies['connect.sid']->value;
$this->setLoggerChannel('Overseerr')->info('Grabbed token');
} else {
if ($fallback) {
$this->setLoggerChannel('Overseerr')->warning('Overseerr did not return Token - Will retry using fallback credentials');
} else {
$this->setLoggerChannel('Overseerr')->warning('Overseerr did not return Token');
}
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Overseerr')->error($e);
}
if ($token) {
return urldecode($token);
} elseif ($fallback) {
return $this->getOverseerrToken($this->config['overseerrFallbackUser'], $this->decrypt($this->config['overseerrFallbackPassword']), null, false);
} else {
return false;
}
}
public function getPetioToken($username, $password, $oAuthToken = null, $fallback = false)
{
$token = null;
try {
$url = $this->qualifyURL($this->config['petioURL']);
$headers = array(
"Content-Type" => "application/json",
"X-Forwarded-For" => $this->userIP()
);
$data = array(
'user' => [
'username' => ($oAuthToken ? '' : $username),
'password' => ($oAuthToken ? '' : $password),
'type' => 1,
],
'authToken' => false,
'token' => $oAuthToken
);
$endpoint = ($oAuthToken) ? '/api/login/plex_login' : '/api/login';
$options = $this->requestOptions($url, $this->getSSOTimeout());
$response = Requests::post($url . $endpoint, $headers, json_encode($data), $options);
if ($response->success) {
$user = json_decode($response->body, true)['user'];
$token = json_decode($response->body, true)['token'];
$this->setLoggerChannel('Petio')->info('Grabbed token');
} else {
if ($fallback) {
$this->setLoggerChannel('Petio')->warning('Petio did not return Token - Will retry using fallback credentials');
} else {
$this->setLoggerChannel('Petio')->warning('Petio did not return Token');
}
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Petio')->error($e);
}
if ($token) {
return $token;
} elseif ($fallback) {
return $this->getPetioToken($this->config['petioFallbackUser'], $this->decrypt($this->config['petioFallbackPassword']), null, false);
} else {
return false;
}
}
}

View File

@@ -0,0 +1,6 @@
<?php
trait StaticFunctions
{
}

View File

@@ -0,0 +1,2 @@
<?php
/* Depreciated */

View File

@@ -0,0 +1,56 @@
<?php
trait TokenFunctions
{
public function configToken()
{
return Lcobucci\JWT\Configuration::forSymmetricSigner(
// You may use any HMAC variations (256, 384, and 512)
new Lcobucci\JWT\Signer\Hmac\Sha256(),
// replace the value below with a key of your own!
Lcobucci\JWT\Signer\Key\InMemory::plainText($this->config['organizrHash'])
// You may also override the JOSE encoder/decoder if needed by providing extra arguments here
);
}
public function validationConstraints()
{
return [
new Lcobucci\JWT\Validation\Constraint\IssuedBy('Organizr'),
new Lcobucci\JWT\Validation\Constraint\PermittedFor('Organizr'),
new Lcobucci\JWT\Validation\Constraint\LooseValidAt(Lcobucci\Clock\SystemClock::fromUTC())
];
}
public function jwtParse($userToken)
{
try {
$result = [];
// Check Token with JWT
// Set key
if (!isset($this->config['organizrHash'])) {
return null;
}
$config = $this->configToken();
assert($config instanceof Lcobucci\JWT\Configuration);
$token = $config->parser()->parse($userToken);
assert($token instanceof Lcobucci\JWT\UnencryptedToken);
$constraints = $this->validationConstraints();
if (!$config->validator()->validate($token, ...$constraints)) {
return false;
}
$result['username'] = ($token->claims()->has('name')) ? $token->claims()->get('name') : 'N/A';
$result['group'] = ($token->claims()->has('group')) ? $token->claims()->get('group') : 'N/A';
$result['groupID'] = $token->claims()->get('groupID');
$result['userID'] = $token->claims()->get('userID');
$result['email'] = $token->claims()->get('email');
$result['image'] = $token->claims()->get('image');
$result['tokenExpire'] = $token->claims()->get('exp');
$result['tokenDate'] = $token->claims()->get('iat');
return $result;
} catch (\OutOfBoundsException | \RunTimeException | \InvalidArgumentException | \Lcobucci\JWT\Validation\RequiredConstraintsViolated $e) {
$this->setLoggerChannel('Token Error')->error($e);
return false;
}
}
}

View File

@@ -0,0 +1,248 @@
<?php
trait UpdateFunctions
{
public function testScriptFilePermissions($script, $retest = false)
{
if (file_exists($script)) {
if (is_executable($script)) {
return true;
} elseif (!$retest) {
$this->setLoggerChannel('Update')->notice('Attempting to set correct permissions', ['file' => $script]);
$permissions = shell_exec('chmod 777 ' . $script);
return $this->testScriptFilePermissions($script, true);
} else {
$this->setLoggerChannel('Update')->warning('Update script doesn\'t have the correct permissions', ['file' => $script]);
return false;
}
} else {
$this->setLoggerChannel('Update')->warning('Update script doesn\'t exist', ['file' => $script]);
return false;
}
}
public function updateOrganizr()
{
if ($this->docker) {
return $this->dockerUpdate();
} elseif ($this->getOS() == 'win') {
return $this->windowsUpdate();
} else {
return $this->linuxUpdate();
}
}
public function createUpdateStatusFile()
{
$file = $this->config['dbLocation'] . 'updateInProgress.txt';
touch($file);
return true;
}
public function removeUpdateStatusFile()
{
$file = $this->config['dbLocation'] . 'updateInProgress.txt';
if (file_exists($file)) {
@unlink($file);
}
return true;
}
public function hasUpdateStatusFile()
{
return file_exists($this->config['dbLocation'] . 'updateInProgress.txt');
}
public function checkForGitLockFile()
{
return file_exists($this->root . DIRECTORY_SEPARATOR . '.git' . DIRECTORY_SEPARATOR . 'index.lock');
}
public function removeGitLockFile()
{
if ($this->checkForGitLockFile()) {
$removed = false;
if (@unlink($this->root . DIRECTORY_SEPARATOR . '.git' . DIRECTORY_SEPARATOR . 'index.lock')) {
$removed = true;
}
return $removed;
}
return true;
}
public function dockerUpdate()
{
if (!$this->docker) {
$this->setResponse(409, 'Your install type is not Docker');
return false;
}
if ($this->hasUpdateStatusFile()) {
$this->setResponse(500, 'Already Update in progress');
return false;
} else {
$this->createUpdateStatusFile();
}
if (!$this->removeGitLockFile()) {
$this->setResponse(500, 'Git Lock file was found and could not be removed. Please remove manually');
return false;
}
$dockerUpdate = null;
ini_set('max_execution_time', 0);
set_time_limit(0);
chdir('/etc/cont-init.d/');
if (file_exists('./30-install')) {
$this->setAPIResponse('error', 'Update failed - OrgTools is deprecated - please use organizr/organizr', 500);
return false;
} elseif (file_exists('./40-install')) {
$dockerUpdate = shell_exec('./40-install');
}
$this->removeUpdateStatusFile();
if ($dockerUpdate) {
$this->setAPIResponse('success', $dockerUpdate, 200);
return true;
} else {
$this->setAPIResponse('error', 'Update failed', 500);
return false;
}
}
public function windowsUpdate()
{
if ($this->docker || $this->getOS() !== 'win') {
$this->setResponse(409, 'Your install type is not Windows');
return false;
}
if ($this->hasUpdateStatusFile()) {
$this->setResponse(500, 'Already Update in progress');
return false;
} else {
$this->createUpdateStatusFile();
}
$branch = ($this->config['branch'] == 'v2-master') ? '-m' : '-d';
ini_set('max_execution_time', 0);
set_time_limit(0);
$logFile = $this->root . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'log.txt';
$windowsScript = $this->root . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'windows-update.bat ' . $branch . ' > ' . $logFile . ' 2>&1';
$windowsUpdate = shell_exec($windowsScript);
$this->removeUpdateStatusFile();
if ($windowsUpdate) {
$this->setAPIResponse('success', $windowsUpdate, 200);
return true;
} else {
$this->setAPIResponse('success', 'Update Complete - check log.txt for output', 200);
return false;
}
}
public function linuxUpdate()
{
if ($this->docker || $this->getOS() == 'win') {
$this->setResponse(409, 'Your install type is not Linux');
return false;
}
if ($this->hasUpdateStatusFile()) {
$this->setResponse(500, 'Already Update in progress');
return false;
} else {
$this->createUpdateStatusFile();
}
$branch = $this->config['branch'];
ini_set('max_execution_time', 0);
set_time_limit(0);
$logFile = $this->root . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'log.txt';
$script = $this->root . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'linux-update.sh';
$scriptExec = $script . ' ' . $branch . ' > ' . $logFile . ' 2>&1';
$checkScript = $this->testScriptFilePermissions($script);
if (!$checkScript) {
$this->setResponse(500, 'Update script permissions error');
$this->removeUpdateStatusFile();
return false;
}
$update = shell_exec($scriptExec);
$this->removeUpdateStatusFile();
if ($update) {
$this->setAPIResponse('success', $update, 200);
return true;
} else {
$this->setAPIResponse('success', 'Update Complete - check log.txt for output', 200);
return false;
}
}
public function upgradeInstall($branch = 'v2-master', $stage = '1')
{
// may kill this function in place for php script to run elsewhere
if ($this->docker) {
$this->setAPIResponse('error', 'Cannot perform update action on docker install - use script', 500);
return false;
}
if ($this->getOS() == 'win') {
$this->setAPIResponse('error', 'Cannot perform update action on windows install - use script', 500);
return false;
}
$notWritable = array_search(false, $this->pathsWritable($this->paths));
if ($notWritable == false) {
ini_set('max_execution_time', 0);
set_time_limit(0);
$url = 'https://github.com/causefx/Organizr/archive/' . $branch . '.zip';
$file = "upgrade.zip";
$source = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'upgrade' . DIRECTORY_SEPARATOR . 'Organizr-' . str_replace('v2', '2', $branch) . DIRECTORY_SEPARATOR;
$cleanup = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . "upgrade" . DIRECTORY_SEPARATOR;
$destination = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR;
switch ($stage) {
case '1':
$this->setLoggerChannel('Update')->info('Started Upgrade Process');
if ($this->downloadFile($url, $file)) {
$this->setLoggerChannel('Update')->info('Downloaded Update File for Branch: ' . $branch);
$this->setAPIResponse('success', 'Downloaded file successfully', 200);
return true;
} else {
$this->setLoggerChannel('Update')->warning('Downloaded Update File Failed for Branch: ' . $branch);
$this->setAPIResponse('error', 'Download failed', 500);
return false;
}
case '2':
if ($this->unzipFile($file)) {
$this->setLoggerChannel('Update')->info('Unzipped Update File for Branch: ' . $branch);
$this->setAPIResponse('success', 'Unzipped file successfully', 200);
return true;
} else {
$this->setLoggerChannel('Update')->warning('Unzip Failed for Branch: ' . $branch);
$this->setAPIResponse('error', 'Unzip failed', 500);
return false;
}
case '3':
if ($this->rcopy($source, $destination)) {
$this->setLoggerChannel('Update')->info('Files overwritten using Updated Files from Branch: ' . $branch);
$updateComplete = $this->config['dbLocation'] . 'completed.txt';
if (!file_exists($updateComplete)) {
touch($updateComplete);
}
$this->setAPIResponse('success', 'Files replaced successfully', 200);
return true;
} else {
$this->setLoggerChannel('Update')->warning('Overwrite Failed for Branch: ' . $branch);
$this->setAPIResponse('error', 'File replacement failed', 500);
return false;
}
case '4':
if ($this->rrmdir($cleanup)) {
$this->setLoggerChannel('Update')->info('Deleted Update Files from Branch: ' . $branch);
$this->setLoggerChannel('Update')->info('Update Completed');
$this->setAPIResponse('success', 'Removed update files successfully', 200);
return true;
} else {
$this->setLoggerChannel('Update')->warning('Removal of Update Files Failed for Branch: ' . $branch);
$this->setAPIResponse('error', 'File removal failed', 500);
return false;
}
default:
$this->setAPIResponse('error', 'Action not setup', 500);
return false;
}
} else {
$this->setAPIResponse('error', 'File permissions not set correctly', 500);
return false;
}
}
}

View File

@@ -0,0 +1,768 @@
<?php
trait UpgradeFunctions
{
public function upgradeCheck()
{
if (!$this->checkForUpdates) {
return true;
}
if ($this->hasDB()) {
$tempLock = $this->config['dbLocation'] . 'DBLOCK.txt';
$updateComplete = $this->config['dbLocation'] . 'completed.txt';
$cleanup = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'upgrade' . DIRECTORY_SEPARATOR;
if (file_exists($updateComplete)) {
@unlink($updateComplete);
@$this->rrmdir($cleanup);
}
if (file_exists($tempLock)) {
die($this->showHTML('Upgrading', 'Please wait...'));
}
$updateDB = false;
$updateSuccess = true;
$compare = new Composer\Semver\Comparator;
$oldVer = $this->config['configVersion'];
// Upgrade check start for version below
$versionCheck = '2.0.0-beta-200';
if ($compare->lessThan($oldVer, $versionCheck)) {
$updateDB = true;
$oldVer = $versionCheck;
}
// End Upgrade check start for version above
// Upgrade check start for version below
$versionCheck = '2.0.0-beta-500';
if ($compare->lessThan($oldVer, $versionCheck)) {
$updateDB = true;
$oldVer = $versionCheck;
}
// End Upgrade check start for version above
// Upgrade check start for version below
$versionCheck = '2.0.0-beta-800';
if ($compare->lessThan($oldVer, $versionCheck)) {
$updateDB = true;
$oldVer = $versionCheck;
}
// End Upgrade check start for version above
// Upgrade check start for version below
$versionCheck = '2.1.0';
if ($compare->lessThan($oldVer, $versionCheck)) {
$updateDB = false;
$oldVer = $versionCheck;
$this->upgradeToVersion($versionCheck);
}
// End Upgrade check start for version above
// Upgrade check start for version below
$versionCheck = '2.1.400';
if ($compare->lessThan($oldVer, $versionCheck)) {
$updateDB = false;
$oldVer = $versionCheck;
$this->upgradeToVersion($versionCheck);
}
// End Upgrade check start for version above
// Upgrade check start for version below
$versionCheck = '2.1.525';
if ($compare->lessThan($oldVer, $versionCheck)) {
$updateDB = false;
$oldVer = $versionCheck;
$this->upgradeToVersion($versionCheck);
}
// End Upgrade check start for version above
// Upgrade check start for version below
$versionCheck = '2.1.860';
if ($compare->lessThan($oldVer, $versionCheck)) {
$updateDB = false;
$oldVer = $versionCheck;
$this->upgradeToVersion($versionCheck);
}
// End Upgrade check start for version above
// Upgrade check start for version below
$versionCheck = '2.1.1500';
if ($compare->lessThan($oldVer, $versionCheck)) {
$updateDB = false;
$oldVer = $versionCheck;
$this->upgradeToVersion($versionCheck);
}
// End Upgrade check start for version above
// Upgrade check start for version below
$versionCheck = '2.1.1860';
if ($compare->lessThan($oldVer, $versionCheck)) {
$updateDB = false;
$oldVer = $versionCheck;
$this->upgradeToVersion($versionCheck);
}
// End Upgrade check start for version above
// Upgrade check start for version below
$versionCheck = '2.1.2200';
if ($compare->lessThan($oldVer, $versionCheck)) {
$updateDB = false;
$oldVer = $versionCheck;
$this->upgradeToVersion($versionCheck);
}
// End Upgrade check start for version above
if ($updateDB == true) {
//return 'Upgraded Needed - Current Version '.$oldVer.' - New Version: '.$versionCheck;
// Upgrade database to latest version
$updateSuccess = $this->updateDB($oldVer);
}
// Update config.php version if different to the installed version
if ($updateSuccess && $this->version !== $this->config['configVersion']) {
$this->updateConfig(array('apply_CONFIG_VERSION' => $this->version));
$this->setLoggerChannel('Update')->notice('Updated config version to ' . $this->version);
}
if ($updateSuccess == false) {
die($this->showHTML('Database update failed', 'Please manually check logs and fix - Then reload this page'));
}
return true;
}
}
public function dropColumnFromDatabase($table = '', $columnName = '')
{
if ($table == '' || $columnName == '') {
return false;
}
if ($this->hasDB()) {
$columnExists = $this->checkIfColumnExists($table, $columnName);
if ($columnExists) {
$columnAlter = [
array(
'function' => 'query',
'query' => ['ALTER TABLE %n DROP %n',
(string)$table,
(string)$columnName,
]
)
];
$AlterQuery = $this->processQueries($columnAlter);
return (boolean)($AlterQuery);
}
}
return false;
}
public function checkIfColumnExists($table = '', $columnName = '')
{
if ($table == '' || $columnName == '') {
return false;
}
if ($this->hasDB()) {
if ($this->config['driver'] === 'sqlite3') {
$term = 'SELECT COUNT(*) AS has_column FROM pragma_table_info(?) WHERE name=?';
} else {
$term = 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "' . $this->config['dbName'] . '" AND TABLE_NAME=? AND COLUMN_NAME=?';
}
$tableInfo = [
array(
'function' => 'fetchSingle',
'query' => array(
$term,
(string)$table,
(string)$columnName
)
)
];
$query = $this->processQueries($tableInfo);
return (boolean)($query);
}
}
public function addColumnToDatabase($table = '', $columnName = '', $definition = 'TEXT')
{
if ($table == '' || $columnName == '' || $definition == '') {
return false;
}
if ($this->hasDB()) {
if ($this->config['driver'] === 'sqlite3') {
$term = 'SELECT COUNT(*) AS has_column FROM pragma_table_info(?) WHERE name=?';
} else {
$term = 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "' . $this->config['dbName'] . '" AND TABLE_NAME=? AND COLUMN_NAME=?';
}
$tableInfo = [
array(
'function' => 'fetchSingle',
'query' => array(
$term,
(string)$table,
(string)$columnName
)
)
];
$query = $this->processQueries($tableInfo);
if (!$query) {
$columnAlter = [
array(
'function' => 'query',
'query' => ['ALTER TABLE %n ADD %n ' . (string)$definition,
(string)$table,
(string)$columnName,
]
)
];
$AlterQuery = $this->processQueries($columnAlter);
if ($AlterQuery) {
$query = $this->processQueries($tableInfo);
if ($query) {
return true;
}
}
} else {
return true;
}
}
return false;
}
public function createMysqliDatabase($database, $migration = false)
{
$query = [
array(
'function' => 'fetchAll',
'query' => array(
'DROP DATABASE IF EXISTS tempMigration'
)
),
array(
'function' => 'fetchAll',
'query' => array(
'CREATE DATABASE IF NOT EXISTS %n',
$database
)
),
];
//$query = ['CREATE DB %n', $database];
return $this->processQueries($query, $migration);
}
public function updateDB($oldVerNum = false)
{
$tempLock = $this->config['dbLocation'] . 'DBLOCK.txt';
if (!file_exists($tempLock)) {
touch($tempLock);
$migrationDB = 'tempMigration.db';
$pathDigest = pathinfo($this->config['dbLocation'] . $this->config['dbName']);
// Delete old backup sqlite db if exists
if (file_exists($this->config['dbLocation'] . $migrationDB)) {
unlink($this->config['dbLocation'] . $migrationDB);
}
// Create Temp DB First
$this->createNewDB('tempMigration', true);
$this->connectOtherDB();
if ($this->config['driver'] == 'sqlite3') {
// Backup sqlite database
$backupDB = $pathDigest['dirname'] . '/' . $pathDigest['filename'] . '[' . date('Y-m-d_H-i-s') . ']' . ($oldVerNum ? '[' . $oldVerNum . ']' : '') . '.bak.db';
copy($this->config['dbLocation'] . $this->config['dbName'], $backupDB);
}
$success = $this->createDB($this->config['dbLocation'], true);
if ($success) {
switch ($this->config['driver']) {
case 'sqlite3':
$query = 'SELECT name FROM sqlite_master WHERE type="table"';
break;
case 'mysqli':
$query = 'SELECT Table_name as name from information_schema.tables where table_schema = "tempMigration"';
break;
}
$response = [
array(
'function' => 'fetchAll',
'query' => array(
$query
)
),
];
$tables = $this->processQueries($response);
$defaultTables = $this->getDefaultTablesFormatted();
foreach ($tables as $table) {
if (in_array($table['name'], $defaultTables)) {
$response = [
array(
'function' => 'fetchAll',
'query' => array(
'SELECT * FROM %n', $table['name']
)
),
];
$data = $this->processQueries($response);
$this->setLoggerChannel('Migration')->info('Obtained Table data', ['table' => $table['name']]);
foreach ($data as $row) {
$response = [
array(
'function' => 'query',
'query' => array(
'INSERT into %n', $table['name'],
$row
)
),
];
$this->processQueries($response, true);
}
$this->setLoggerChannel('Migration')->info('Wrote Table data', ['table' => $table['name']]);
}
}
if ($this->config['driver'] == 'mysqli') {
$response = [
array(
'function' => 'query',
'query' => array(
'DROP DATABASE IF EXISTS %n', $this->config['dbName']
)
),
];
$data = $this->processQueries($response);
if ($data) {
$create = $this->createNewDB($this->config['dbName']);
if ($create) {
$structure = $this->createDB($this->config['dbLocation']);
if ($structure) {
foreach ($tables as $table) {
if (in_array($table['name'], $defaultTables)) {
$response = [
array(
'function' => 'fetchAll',
'query' => array(
'SELECT * FROM %n', $table['name']
)
),
];
$data = $this->processQueries($response, true);
$this->setLoggerChannel('Migration')->info('Obtained Table data', ['table' => $table['name']]);
foreach ($data as $row) {
$response = [
array(
'function' => 'query',
'query' => array(
'INSERT into %n', $table['name'],
$row
)
),
];
$this->processQueries($response);
}
$this->setLoggerChannel('Migration')->info('Wrote Table data', ['table' => $table['name']]);
}
}
} else {
$this->setLoggerChannel('Migration')->warning('Could not recreate Database structure');
}
} else {
$this->setLoggerChannel('Migration')->warning('Could not recreate Database');
}
} else {
$this->setLoggerChannel('Migration')->warning('Could not drop old tempMigration Database');
}
$this->setLoggerChannel('Migration')->info('All Table data converted');
@unlink($tempLock);
return true;
}
//$this->db->disconnect();
//$this->otherDb->disconnect();
// Remove Current Database
if ($this->config['driver'] == 'sqlite3') {
$this->setLoggerChannel('Migration')->info('All Table data converted');
$this->setLoggerChannel('Migration')->info('Starting Database movement for sqlite3');
if (file_exists($this->config['dbLocation'] . $migrationDB)) {
$oldFileSize = filesize($this->config['dbLocation'] . $this->config['dbName']);
$newFileSize = filesize($this->config['dbLocation'] . $migrationDB);
if ($newFileSize > 0) {
$this->setLoggerChannel('Migration')->info('New Table size has been verified');
@unlink($this->config['dbLocation'] . $this->config['dbName']);
copy($this->config['dbLocation'] . $migrationDB, $this->config['dbLocation'] . $this->config['dbName']);
@unlink($this->config['dbLocation'] . $migrationDB);
$this->setLoggerChannel('Migration')->info('Migrated Old Info to new Database');
@unlink($tempLock);
return true;
} else {
$this->setLoggerChannel('Migration')->warning('Database filesize is zero');
}
} else {
$this->setLoggerChannel('Migration')->warning('Migration Database does not exist');
}
}
@unlink($tempLock);
return false;
} else {
$this->setLoggerChannel('Migration')->warning('Could not create migration Database');
}
@unlink($tempLock);
return false;
}
return false;
}
public function resetUpdateFeature($feature = null)
{
if (!$feature) {
$this->setResponse(409, 'Feature not supplied');
return false;
}
$feature = strtolower($feature);
switch ($feature) {
case 'groupmax':
case 'groupidmax':
case 'group-max':
case 'group-id-max':
case 'group_id':
case 'group_id_max':
$columnExists = $this->checkIfColumnExists('tabs', 'group_id_max');
if ($columnExists) {
$query = [
[
'function' => 'query',
'query' => [
'UPDATE tabs SET group_id_max=0'
]
],
];
$tabs = $this->processQueries($query);
if (!$tabs) {
$this->setResponse(500, 'An error occurred');
return false;
}
} else {
$this->setResponse(500, 'group_id_max column does not exist');
return false;
}
break;
default:
$this->setResponse(404, 'Feature not found in reset update');
return false;
}
$this->setResponse(200, 'Ran reset update feature for ' . $feature);
return true;
}
public function upgradeToVersion($version = '2.1.0')
{
$this->setLoggerChannel('Upgrade')->notice('Starting upgrade to version ' . $version);
switch ($version) {
case '2.1.0':
$this->upgradeSettingsTabURL();
$this->upgradeHomepageTabURL();
break;
case '2.1.400':
$this->removeOldPluginDirectoriesAndFiles();
break;
case '2.1.525':
$this->removeOldCustomHTML();
break;
case '2.1.860':
$this->upgradeInstalledPluginsConfigItem();
break;
case '2.1.1500':
$this->upgradeDataToFolder();
break;
case '2.1.1860':
$this->upgradePluginsToDataFolder();
break;
case '2.1.2200':
$this->backupOrganizr();
$this->addGroupIdMaxToDatabase();
$this->addAddToAdminToDatabase();
break;
}
$this->setLoggerChannel('Upgrade')->notice('Finished upgrade to version ' . $version);
$this->setAPIResponse('success', 'Ran update function for version: ' . $version, 200);
return true;
}
public function removeOldCacheFolder()
{
$folder = $this->root . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$this->setLoggerChannel('Migration');
$this->logger->info('Running Old Cache folder migration');
if (file_exists($folder)) {
$this->rrmdir($folder);
$this->logger->info('Old Cache folder found');
$this->logger->info('Removed Old Cache folder');
}
return true;
}
public function upgradeDataToFolder()
{
if ($this->hasDB()) {
// Make main data folder
$rootFolderMade = $this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data');
// Make config folder child
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR);
if ($rootFolderMade) {
// Migrate over userTabs folder
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'userTabs');
if ($this->rcopy($this->root . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'userTabs', $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'userTabs')) {
// Convert tabs over
$query = [
[
'function' => 'fetchAll',
'query' => [
'SELECT * FROM tabs WHERE image like "%userTabs%"'
]
],
];
$tabs = $this->processQueries($query);
if (count($tabs) > 0) {
foreach ($tabs as $tab) {
$newImage = str_replace('plugins/images/userTabs', 'data/userTabs', $tab['image']);
$updateQuery = [
[
'function' => 'query',
'query' => [
'UPDATE tabs SET',
['image' => $newImage],
'WHERE id = ?',
$tab['id']
]
],
];
$this->processQueries($updateQuery);
}
}
$this->setLoggerChannel('Migration');
$this->logger->info('The folder "userTabs" was migrated to new data folder');
}
// Migrate over custom cert
if (file_exists($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'custom.pem')) {
// Make cert folder child
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR);
if ($this->rcopy($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'custom.pem', $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'custom.pem')) {
$this->setLoggerChannel('Migration');
$this->logger->info('Moved over custom cert file');
}
}
// Migrate over favIcon
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'favicon');
if ($this->rcopy($this->root . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'faviconCustom', $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'favicon')) {
if ($this->config['favIcon'] !== '') {
$this->config['favIcon'] = str_replace('plugins/images/faviconCustom', 'data/favicon', $this->config['favIcon']);
$this->updateConfig(array('favIcon' => $this->config['favIcon']));
}
$this->setLoggerChannel('Migration');
$this->logger->info('Favicon was migrated over');
}
// Migrate over custom pages
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'pages');
if (file_exists($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'custom')) {
if ($this->rcopy($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'custom', $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'pages')) {
$this->rrmdir($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'custom');
$this->setLoggerChannel('Migration');
$this->logger->info('Custom pages was migrated over');
}
}
// Migrate over custom routes
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'routes');
if (file_exists($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'v2' . DIRECTORY_SEPARATOR . 'routes' . DIRECTORY_SEPARATOR . 'custom')) {
if ($this->rcopy($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'v2' . DIRECTORY_SEPARATOR . 'routes' . DIRECTORY_SEPARATOR . 'custom', $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'routes')) {
$this->rrmdir($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'v2' . DIRECTORY_SEPARATOR . 'routes' . DIRECTORY_SEPARATOR . 'custom');
$this->setLoggerChannel('Migration');
$this->logger->info('Custom routes was migrated over');
}
}
// Migrate over cache folder
$this->removeOldCacheFolder();
}
return true;
}
return false;
}
public function upgradePluginsToDataFolder()
{
if ($this->hasDB()) {
// Make main data folder
$rootFolderMade = $this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data');
if ($rootFolderMade) {
// Migrate over plugins folder
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'plugins');
$plexLibraries = $this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'plexLibraries';
if (file_exists($plexLibraries)) {
if (rename($plexLibraries, $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'plexLibraries')) {
$this->setLoggerChannel('Migration');
$this->logger->info('The plugin folder "plexLibraries" was migrated to new data folder');
}
}
$test = $this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'test';
if (file_exists($test)) {
if (rename($test, $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'test')) {
$this->setLoggerChannel('Migration');
$this->logger->info('The plugin folder "test" was migrated to new data folder');
}
}
}
return true;
}
return false;
}
public function upgradeSettingsTabURL()
{
$response = [
array(
'function' => 'query',
'query' => array(
'UPDATE tabs SET',
['url' => 'api/v2/page/settings'],
'WHERE url = ?',
'api/?v1/settings/page'
)
),
];
return $this->processQueries($response);
}
public function upgradeHomepageTabURL()
{
$response = [
array(
'function' => 'query',
'query' => array(
'UPDATE tabs SET',
['url' => 'api/v2/page/homepage'],
'WHERE url = ?',
'api/?v1/homepage/page'
)
),
];
return $this->processQueries($response);
}
public function upgradeInstalledPluginsConfigItem()
{
$oldConfigItem = $this->config['installedPlugins'];
if (gettype($oldConfigItem) == 'string') {
if ((strpos($oldConfigItem, '|') !== false)) {
$newPlugins = [];
$plugins = explode('|', $oldConfigItem);
foreach ($plugins as $plugin) {
$info = explode(':', $plugin);
$newPlugins[$info[0]] = [
'name' => $info[0],
'version' => $info[1],
'repo' => 'organizr'
];
}
} else {
$newPlugins = [];
if ($oldConfigItem !== '') {
$info = explode(':', $oldConfigItem);
$newPlugins[$info[0]] = [
'name' => $info[0],
'version' => $info[1],
'repo' => 'https://github.com/Organizr/Organizr-Plugins'
];
}
}
$this->updateConfig(['installedPlugins' => $newPlugins]);
} elseif (gettype($oldConfigItem) == 'array') {
$this->updateConfig(['installedPlugins' => $oldConfigItem]);
}
return true;
}
public function removeOldPluginDirectoriesAndFiles()
{
$folders = [
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'api',
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'config',
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'css',
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'js',
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'misc',
];
$files = [
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'bookmark.php',
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'chat.php',
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'healthChecks.php',
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'invites.php',
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'php-mailer.php',
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'speedTest.php',
];
foreach ($files as $file) {
if (file_exists($file)) {
@unlink($file);
}
}
foreach ($folders as $folder) {
if (file_exists($folder)) {
@$this->rrmdir($folder);
}
}
return true;
}
public function checkForConfigKeyAddToArray($keys)
{
$updateItems = [];
foreach ($keys as $new => $old) {
if (isset($this->config[$old])) {
if ($this->config[$old] !== '') {
$updateItemsNew = [$new => $this->config[$old]];
$updateItems = array_merge($updateItems, $updateItemsNew);
}
}
}
return $updateItems;
}
public function removeOldCustomHTML()
{
$backup = $this->backupOrganizr();
if ($backup) {
$keys = [
'homepageCustomHTML01Enabled' => 'homepageCustomHTMLoneEnabled',
'homepageCustomHTML01Auth' => 'homepageCustomHTMLoneAuth',
'customHTML01' => 'customHTMLone',
'homepageCustomHTML02Enabled' => 'homepageCustomHTMLtwoEnabled',
'homepageCustomHTML02Auth' => 'homepageCustomHTMLtwoAuth',
'customHTML02' => 'customHTMLtwo',
];
$updateItems = $this->checkForConfigKeyAddToArray($keys);
$updateComplete = false;
if (!empty($updateItems)) {
$updateComplete = $this->updateConfig($updateItems);
}
if ($updateComplete) {
$removeConfigItems = $this->removeConfigItem(['homepagCustomHTMLoneAuth', 'homepagCustomHTMLoneEnabled', 'homepagCustomHTMLtwoAuth', 'homepagCustomHTMLtwoEnabled', 'homepageOrdercustomhtml', 'homepageOrdercustomhtmlTwo', 'homepageCustomHTMLoneEnabled', 'homepageCustomHTMLoneAuth', 'customHTMLone', 'homepageCustomHTMLtwoEnabled', 'homepageCustomHTMLtwoAuth', 'customHTMLtwo']);
if ($removeConfigItems) {
return true;
}
}
}
return false;
}
public function addGroupIdMaxToDatabase()
{
$this->setLoggerChannel('Database Migration')->info('Starting database update');
$hasOldColumn = $this->checkIfColumnExists('tabs', 'group_id_min');
if ($hasOldColumn) {
$this->setLoggerChannel('Database Migration')->info('Cleaning up database by removing old group_id_min');
$removeColumn = $this->dropColumnFromDatabase('tabs', 'group_id_min');
if ($removeColumn) {
$this->setLoggerChannel('Database Migration')->info('Removed group_id_min from database');
} else {
$this->setLoggerChannel('Database Migration')->warning('Error removing group_id_min from database');
}
}
$addColumn = $this->addColumnToDatabase('tabs', 'group_id_max', 'INTEGER DEFAULT \'0\'');
if ($addColumn) {
$this->setLoggerChannel('Database Migration')->notice('Added group_id_max to database');
return true;
} else {
$this->setLoggerChannel('Database Migration')->warning('Could not update database');
return false;
}
}
public function addAddToAdminToDatabase()
{
$this->setLoggerChannel('Database Migration')->info('Starting database update');
$addColumn = $this->addColumnToDatabase('tabs', 'add_to_admin', 'INTEGER DEFAULT \'0\'');
if ($addColumn) {
$this->setLoggerChannel('Database Migration')->notice('Added add_to_admin to database');
return true;
} else {
$this->setLoggerChannel('Database Migration')->warning('Could not update database');
return false;
}
}
}

View File

@@ -0,0 +1,176 @@
<?php
trait AdGuardHomepageItem
{
public function adguardSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'AdGuardHome',
'enabled' => true,
'image' => 'plugins/images/tabs/AdGuardHome.png',
'category' => 'Monitor',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageAdGuardEnabled'),
$this->settingsOption('auth', 'homepageAdGuardAuth'),
],
'Connection' => [
$this->settingsOption('url', 'adguardURL', ['help' => 'Please make sure to use local IP address and port. You can add multiple AdGuard Homes by comma separating the URLs.', 'placeholder' => 'http(s)://hostname:port']),
$this->settingsOption('username', 'adGuardUsername'),
$this->settingsOption('password', 'adGuardPassword'),
],
'Misc' => [
$this->settingsOption('toggle-title', 'adguardHeaderToggle'),
$this->settingsOption('switch', 'homepageAdGuardCombine', ['label' => 'Combine stat cards', 'help' => 'This controls whether to combine the stats for multiple adguard instances into 1 card.']),
],
'Stats' => [
$this->settingsOption('switch', 'adguardQueriesToggle', ['label' => 'Total Queries']),
$this->settingsOption('switch', 'adguardQueriesBlockedToggle', ['label' => 'Queries Blocked']),
$this->settingsOption('switch', 'adguardPercentToggle', ['label' => 'Percent Blocked']),
$this->settingsOption('switch', 'adguardProcessingToggle', ['label' => 'Processing Time']),
$this->settingsOption('switch', 'adguardDomainListToggle', ['label' => 'Domains on Blocklist']),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'adguard'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionAdGuard()
{
if (empty($this->config['adguardURL'])) {
$this->setAPIResponse('error', 'AdGuard URL is not defined', 422);
return false;
}
$api = array();
$failed = false;
$errors = '';
$urls = explode(',', $this->config['adguardURL']);
foreach ($urls as $url) {
$url = $url . '/control/stats';
try {
$options = array(
'auth' => array($this->config['adGuardUsername'], $this->decrypt($this->config['adGuardPassword']))
);
$response = Requests::get($url, [], $options);
if ($response->success) {
@$test = json_decode($response->body, true);
if (!is_array($test)) {
$ip = $this->qualifyURL($url, true)['host'];
$errors .= $ip . ': Response was not JSON';
$failed = true;
}
}
if (!$response->success) {
$ip = $this->qualifyURL($url, true)['host'];
$errors .= $ip . ": Unknown Failure";
$failed = true;
}
} catch (Requests_Exception $e) {
$failed = true;
$ip = $this->qualifyURL($url, true)['host'];
$errors .= $ip . ': ' . $e->getMessage();
$this->setLoggerChannel('AdGuard')->error($e);
};
}
if ($failed) {
$this->setAPIResponse('error', $errors, 500);
return false;
} else {
$this->setAPIResponse('success', null, 200);
return true;
}
}
public function adguardHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageAdGuardEnabled'
],
'auth' => [
'homepageAdGuardAuth'
],
'not_empty' => [
'adguardURL'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderAdGuard()
{
if ($this->homepageItemPermissions($this->adguardHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading AdGuard...</h2></div>
<script>
// Pi-hole Stats
homepageAdGuard("' . $this->config['homepageAdGuardRefresh'] . '");
// End Pi-hole Stats
</script>
</div>
';
}
}
public function getAdGuardHomepageStats()
{
if (!$this->homepageItemPermissions($this->adguardHomepagePermissions('main'), true)) {
return false;
}
$stats = array();
$urls = explode(',', $this->config['adguardURL']);
foreach ($urls as $url) {
$stats_url = $url . '/control/stats?';
$filter_url = $url . '/control/filtering/status?';
try {
$options = array(
'auth' => array($this->config['adGuardUsername'], $this->decrypt($this->config['adGuardPassword']))
);
$response = Requests::get($stats_url, [], $options);
if ($response->success) {
@$adguardResults = json_decode($response->body, true);
if (is_array($adguardResults)) {
$ip = $this->qualifyURL($stats_url, true)['host'];
$stats['data'][$ip] = $adguardResults;
}
}
$response = Requests::get($filter_url, [], $options);
if ($response->success) {
@$adguardFilterResults = json_decode($response->body, true);
if (is_array($adguardFilterResults)) {
$ip = $this->qualifyURL($filter_url, true)['host'];
$stats['filters'][$ip] = $adguardFilterResults;
}
}
} catch (Requests_Exception $e) {
$this->setResponse(500, $e->getMessage());
$this->setLoggerChannel('AdGuard')->error($e);
return false;
};
}
$stats['options']['combine'] = $this->config['homepageAdGuardCombine'];
$stats['options']['title'] = $this->config['adguardHeaderToggle'];
$stats['options']['queries'] = $this->config['adguardQueriesToggle'];
$stats['options']['blocked_count'] = $this->config['adguardQueriesBlockedToggle'];
$stats['options']['blocked_percent'] = $this->config['adguardPercentToggle'];
$stats['options']['processing_time'] = $this->config['adguardProcessingToggle'];
$stats['options']['domain_count'] = $this->config['adguardDomainListToggle'];
$stats = isset($stats) ? $stats : null;
$this->setAPIResponse('success', null, 200, $stats);
return $stats;
}
}

View File

@@ -0,0 +1,67 @@
<?php
trait BookmarksHomepageItem
{
public function bookmarksSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Bookmarks',
'enabled' => true,
'image' => 'plugins/images/bookmark.png',
'category' => 'Links',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageBookmarksEnabled'),
$this->settingsOption('auth', 'homepageBookmarksAuth'),
],
'Options' => [
$this->settingsOption('title', 'homepageBookmarksHeader'),
$this->settingsOption('toggle-title', 'homepageBookmarksHeaderToggle'),
$this->settingsOption('enable', 'homepageBookmarksEnabled', ['label' => 'Enable Bookmarks', 'help' => 'Toggles the view module for Bookmarks']),
$this->settingsOption('refresh', 'homepageBookmarksRefresh'),
],
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function bookmarksHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageBookmarksEnabled'
],
'auth' => [
'homepageBookmarksAuth'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderBookmarks()
{
if ($this->homepageItemPermissions($this->bookmarksHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Bookmarks...</h2></div>
<script>
// Bookmarks And Air
homepageBookmarks("' . $this->config['homepageBookmarksRefresh'] . '");
// End Bookmarks And Air
</script>
</div>
';
}
}
}

View File

@@ -0,0 +1,126 @@
<?php
trait CalendarHomepageItem
{
public function calendarSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'iCal',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/calendar.png',
'category' => 'HOMEPAGE',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageCalendarEnabled'),
$this->settingsOption('auth', 'homepageCalendarAuth'),
$this->settingsOption('multiple-url', 'calendariCal', ['label' => 'iCal URL\'s']),
],
'Misc Options' => [
$this->settingsOption('calendar-start', 'calendarStart'),
$this->settingsOption('calendar-end', 'calendarEnd'),
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
$this->settingsOption('calendar-default-view', 'calendarDefault'),
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
$this->settingsOption('calendar-locale', 'calendarLocale'),
$this->settingsOption('calendar-limit', 'calendarLimit'),
$this->settingsOption('refresh', 'calendarRefresh'),
],
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function calendarHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageCalendarEnabled'
],
'auth' => [
'homepageCalendarAuth'
],
'not_empty' => [
'calendariCal'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrdercalendar()
{
if (
$this->homepageItemPermissions($this->sonarrHomepagePermissions('calendar')) ||
$this->homepageItemPermissions($this->radarrHomepagePermissions('calendar')) ||
$this->homepageItemPermissions($this->lidarrHomepagePermissions('calendar')) ||
$this->homepageItemPermissions($this->sickrageHomepagePermissions('calendar')) ||
$this->homepageItemPermissions($this->couchPotatoHomepagePermissions('calendar')) ||
$this->homepageItemPermissions($this->traktHomepagePermissions('calendar')) ||
$this->homepageItemPermissions($this->calendarHomepagePermissions('main'))
) {
return '
<div id="' . __FUNCTION__ . '">
<div id="calendar" class="fc fc-ltr m-b-30"></div>
<script>
// Calendar
homepageCalendar("' . $this->config['calendarRefresh'] . '");
// End Calendar
</script>
</div>
';
}
}
public function loadCalendarJS()
{
$locale = ($this->config['calendarLocale'] !== 'en') ?? false;
return ($locale) ? '<script src="plugins/bower_components/calendar/dist/lang-all.js"></script>' : '';
}
public function getCalendar()
{
$startDate = date('Y-m-d', strtotime("-" . $this->config['calendarStart'] . " days"));
$endDate = date('Y-m-d', strtotime("+" . $this->config['calendarEnd'] . " days"));
$icalCalendarSources = array();
$calendarItems = array();
// SONARR CONNECT
$items = $this->getSonarrCalendar($startDate, $endDate);
$calendarItems = is_array($items) ? array_merge($calendarItems, $items) : $calendarItems;
unset($items);
// LIDARR CONNECT
$items = $this->getLidarrCalendar($startDate, $endDate);
$calendarItems = is_array($items) ? array_merge($calendarItems, $items) : $calendarItems;
unset($items);
// RADARR CONNECT
$items = $this->getRadarrCalendar($startDate, $endDate);
$calendarItems = is_array($items) ? array_merge($calendarItems, $items) : $calendarItems;
unset($items);
// SICKRAGE/BEARD/MEDUSA CONNECT
$items = $this->getSickRageCalendar();
$calendarItems = is_array($items) ? array_merge($calendarItems, $items) : $calendarItems;
unset($items);
// COUCHPOTATO CONNECT
$items = $this->getCouchPotatoCalendar();
$calendarItems = is_array($items) ? array_merge($calendarItems, $items) : $calendarItems;
unset($items);
// TRAKT CONNECT
$items = $this->getTraktCalendar();
$calendarItems = is_array($items) ? array_merge($calendarItems, $items) : $calendarItems;
unset($items);
// iCal URL
$calendarSources['ical'] = $this->getICalendar();
unset($items);
// Finish
$calendarSources['events'] = $calendarItems;
$this->setAPIResponse('success', null, 200, $calendarSources);
return $calendarSources;
}
}

View File

@@ -0,0 +1,180 @@
<?php
trait CouchPotatoHomepageItem
{
public function couchPotatoSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'CouchPotato',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/couchpotato.png',
'category' => 'PVR',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageCouchpotatoEnabled'),
$this->settingsOption('auth', 'homepageCouchpotatoAuth'),
],
'Connection' => [
$this->settingsOption('multiple-url', 'couchpotatoURL'),
$this->settingsOption('multiple-token', 'couchpotatoToken'),
$this->settingsOption('disable-cert-check', 'couchpotatoDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'couchpotatoUseCustomCertificate'),
],
'Misc Options' => [
$this->settingsOption('calendar-start', 'calendarStart'),
$this->settingsOption('calendar-end', 'calendarEnd'),
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
$this->settingsOption('calendar-default-view', 'calendarDefault'),
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
$this->settingsOption('calendar-locale', 'calendarLocale'),
$this->settingsOption('calendar-limit', 'calendarLimit'),
$this->settingsOption('refresh', 'calendarRefresh'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function couchPotatoHomepagePermissions($key = null)
{
$permissions = [
'calendar' => [
'enabled' => [
'homepageCouchpotatoEnabled'
],
'auth' => [
'homepageCouchpotatoAuth'
],
'not_empty' => [
'couchpotatoURL',
'couchpotatoToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function getCouchPotatoCalendar()
{
if (!$this->homepageItemPermissions($this->couchPotatoHomepagePermissions('calendar'), true)) {
return false;
}
$calendarItems = array();
$list = $this->csvHomepageUrlToken($this->config['couchpotatoURL'], $this->config['couchpotatoToken']);
foreach ($list as $key => $value) {
try {
$options = $this->requestOptions($value['url'], 60, $this->config['couchpotatoDisableCertCheck'], $this->config['couchpotatoUseCustomCertificate']);
$downloader = new Kryptonit3\CouchPotato\CouchPotato($value['url'], $value['token'], null, null, $options);
$calendar = $this->formatCouchCalendar($downloader->getMediaList(array('status' => 'active,done')), $key);
} catch (Exception $e) {
$this->setLoggerChannel('Radarr')->error($e);
}
if (!empty($calendar)) {
$calendarItems = array_merge($calendarItems, $calendar);
}
}
$this->setAPIResponse('success', null, 200, $calendarItems);
return $calendarItems;
}
public function formatCouchCalendar($array, $number)
{
$api = json_decode($array, true);
$gotCalendar = array();
$i = 0;
foreach ($api['movies'] as $child) {
$i++;
$movieName = $child['info']['original_title'];
$movieID = $child['info']['tmdb_id'];
if (!isset($movieID)) {
$movieID = "";
}
$physicalRelease = (isset($child['info']['released']) ? $child['info']['released'] : null);
$backupRelease = (isset($child['info']['release_date']['theater']) ? $child['info']['release_date']['theater'] : null);
$physicalRelease = (isset($physicalRelease) ? $physicalRelease : $backupRelease);
$physicalRelease = strtotime($physicalRelease);
$physicalRelease = date("Y-m-d", $physicalRelease);
$oldestDay = new DateTime ($this->currentTime);
$oldestDay->modify('-' . $this->config['calendarStart'] . ' days');
$newestDay = new DateTime ($this->currentTime);
$newestDay->modify('+' . $this->config['calendarEnd'] . ' days');
$startDt = new DateTime ($physicalRelease);
$calendarStartDiff = date_diff($startDt, $newestDay);
$calendarEndDiff = date_diff($startDt, $oldestDay);
if (!$this->calendarDaysCheck($calendarStartDiff->format('%R') . $calendarStartDiff->days, $calendarEndDiff->format('%R') . $calendarEndDiff->days)) {
continue;
}
if (new DateTime() < $startDt) {
$notReleased = "true";
} else {
$notReleased = "false";
}
$downloaded = ($child['status'] == "active") ? "0" : "1";
if ($downloaded == "0" && $notReleased == "true") {
$downloaded = "text-info";
} elseif ($downloaded == "1") {
$downloaded = "text-success";
} else {
$downloaded = "text-danger";
}
if (!empty($child['info']['images']['backdrop_original'])) {
$banner = $child['info']['images']['backdrop_original'][0];
} elseif (!empty($child['info']['images']['backdrop'])) {
$banner = $child['info']['images']['backdrop_original'][0];
} else {
$banner = "/plugins/images/homepage/no-np.png";
}
if ($banner !== "/plugins/images/homepage/no-np.png") {
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$imageURL = $banner;
$cacheFile = $cacheDirectory . $movieID . '.jpg';
$banner = 'data/cache/' . $movieID . '.jpg';
if (!file_exists($cacheFile)) {
$this->cacheImage($imageURL, $movieID);
unset($imageURL);
unset($cacheFile);
}
}
$hasFile = (!empty($child['releases']) && !empty($child['releases'][0]['files']['movie']));
$details = array(
"topTitle" => $movieName,
"bottomTitle" => $child['info']['tagline'],
"status" => $child['status'],
"overview" => $child['info']['plot'],
"runtime" => $child['info']['runtime'],
"image" => $banner,
"ratings" => isset($child['info']['rating']['imdb'][0]) ? $child['info']['rating']['imdb'][0] : '',
"videoQuality" => $hasFile ? $child['releases'][0]['quality'] : "unknown",
"audioChannels" => "",
"audioCodec" => "",
"videoCodec" => "",
"genres" => $child['info']['genres'],
"year" => isset($child['info']['year']) ? $child['info']['year'] : '',
"studio" => isset($child['info']['year']) ? $child['info']['year'] : '',
);
array_push($gotCalendar, array(
"id" => "CouchPotato-" . $number . "-" . $i,
"title" => $movieName,
"start" => $physicalRelease,
"className" => "inline-popups bg-calendar calendar-item movieID--" . $movieID,
"imagetype" => "film " . $downloaded,
"imagetypeFilter" => "film",
"downloadFilter" => $downloaded,
"bgColor" => str_replace('text', 'bg', $downloaded),
"details" => $details
));
}
if ($i != 0) {
return $gotCalendar;
}
return false;
}
}

View File

@@ -0,0 +1,168 @@
<?php
trait DelugeHomepageItem
{
public function delugeSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Deluge',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/deluge.png',
'category' => 'Downloader',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'FYI' => [
$this->settingsOption('html', null, ['override' => 12, 'html' => '
<div class="row">
<div class="col-lg-12">
<div class="panel panel-info">
<div class="panel-heading">
<span lang="en">Notice</span>
</div>
<div class="panel-wrapper collapse in" aria-expanded="true">
<div class="panel-body">
<ul class="list-icons">
<li><i class="fa fa-chevron-right text-danger"></i> <a href="https://github.com/idlesign/deluge-webapi/tree/master/dist" target="_blank">Download Plugin</a></li>
<li><i class="fa fa-chevron-right text-danger"></i> Open Deluge Web UI, go to "Preferences -> Plugins -> Install plugin" and choose egg file.</li>
<li><i class="fa fa-chevron-right text-danger"></i> Activate WebAPI plugin </li>
</ul>
</div>
</div>
</div>
</div>
</div>']
)
],
'Enable' => [
$this->settingsOption('enable', 'homepageDelugeEnabled'),
$this->settingsOption('auth', 'homepageDelugeAuth'),
],
'Connection' => [
$this->settingsOption('url', 'delugeURL'),
$this->settingsOption('password', 'delugePassword', ['help' => 'Note that using a blank password might not work correctly.']),
$this->settingsOption('disable-cert-check', 'delugeDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'delugeUseCustomCertificate'),
],
'Misc Options' => [
$this->settingsOption('hide-seeding', 'delugeHideSeeding'),
$this->settingsOption('hide-completed', 'delugeHideCompleted'),
$this->settingsOption('hide-status', 'delugeHideStatus'),
$this->settingsOption('combine', 'delugeCombine'),
$this->settingsOption('refresh', 'delugeRefresh'),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing. Note that using a blank password might not work correctly.']),
$this->settingsOption('test', 'deluge'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionDeluge()
{
if (!$this->homepageItemPermissions($this->delugeHomepagePermissions('main'), true)) {
return false;
}
try {
$options = $this->requestOptions($this->config['delugeURL'], $this->config['delugeRefresh'], $this->config['delugeDisableCertCheck'], $this->config['delugeUseCustomCertificate'], ['organizr_cert' => $this->getCert(), 'custom_cert' => $this->getCustomCert()]);
$deluge = new deluge($this->config['delugeURL'], $this->decrypt($this->config['delugePassword']), $options);
$torrents = $deluge->getTorrents(null, 'comment, download_payload_rate, eta, hash, is_finished, is_seed, message, name, paused, progress, queue, state, total_size, upload_payload_rate');
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
} catch (Exception $e) {
$this->setLoggerChannel('Deluge')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function delugeHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageDelugeEnabled'
],
'auth' => [
'homepageDelugeAuth'
],
'not_empty' => [
'delugeURL'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderdeluge()
{
if ($this->homepageItemPermissions($this->delugeHomepagePermissions('main'))) {
$loadingBox = ($this->config['delugeCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
$builder = ($this->config['delugeCombine']) ? 'buildDownloaderCombined(\'deluge\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("deluge"));';
return '
<div id="' . __FUNCTION__ . '">
' . $loadingBox . '
<script>
// homepageOrderdeluge
' . $builder . '
homepageDownloader("deluge", "' . $this->config['delugeRefresh'] . '");
// End homepageOrderdeluge
</script>
</div>
';
}
}
public function getDelugeHomepageQueue()
{
if (!$this->homepageItemPermissions($this->delugeHomepagePermissions('main'), true)) {
return false;
}
try {
$options = $this->requestOptions($this->config['delugeURL'], $this->config['delugeRefresh'], $this->config['delugeDisableCertCheck'], $this->config['delugeUseCustomCertificate'], ['organizr_cert' => $this->getCert(), 'custom_cert' => $this->getCustomCert()]);
$deluge = new deluge($this->config['delugeURL'], $this->decrypt($this->config['delugePassword']), $options);
$torrents = $deluge->getTorrents(null, 'comment, download_payload_rate, eta, hash, is_finished, is_seed, message, name, paused, progress, queue, state, total_size, upload_payload_rate, tracker_status');
foreach ($torrents as $key => $value) {
$tempStatus = $this->delugeStatus($value->queue, $value->state, $value->progress);
if ($tempStatus == 'Seeding' && $this->config['delugeHideSeeding']) {
//do nothing
} elseif ($tempStatus == 'Finished' && $this->config['delugeHideCompleted']) {
//do nothing
} else {
if ($this->config['delugeHideStatus']) {
$value->tracker_status = "";
}
$api['content']['queueItems'][] = $value;
}
}
$api['content']['queueItems'] = (empty($api['content']['queueItems'])) ? [] : $api['content']['queueItems'];
$api['content']['historyItems'] = false;
} catch (Exception $e) {
$this->setLoggerChannel('Deluge')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
$api['content'] = $api['content'] ?? false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
public function delugeStatus($queued, $status, $state)
{
if ($queued == '-1' && $state == '100' && ($status == 'Seeding' || $status == 'Queued' || $status == 'Paused')) {
$state = 'Seeding';
} elseif ($state !== '100') {
$state = 'Downloading';
} else {
$state = 'Finished';
}
return ($state) ? $state : $status;
}
}

View File

@@ -0,0 +1,228 @@
<?php
trait DonateHomepageItem
{
public function donateSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Donate',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/donate.png',
'category' => 'Requests',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'About' => [
$this->settingsOption('about', 'Donations', ['about' => 'This item allows you to use Stripe to accept donations on the homepage']),
],
'Setup' => [
$this->settingsOption('html', null, ['label' => 'Instructions', 'override' => 12, 'html' => '
<div class="panel panel-default">
<div class="panel-heading">
<a href="https://dashboard.stripe.com//" target="_blank"><span class="label label-info m-l-5">Visit Stripe Site</span></a>
</div>
<div class="panel-wrapper collapse in">
<div class="panel-body">
<ul class="list-icons">
<li lang="en"><i class="fa fa-caret-right text-info"></i> Create or Login if you already have an account</li>
<li lang="en"><i class="fa fa-caret-right text-info"></i> Goto products and click [Add Product]</li>
<li lang="en"><i class="fa fa-caret-right text-info"></i> Name the product anything you like</li>
<li lang="en"><i class="fa fa-caret-right text-info"></i> Make sure the product has standard pricing</li>
<li lang="en"><i class="fa fa-caret-right text-info"></i> Also make sure that the product is set to <code>One Time</code></li>
<li lang="en"><i class="fa fa-caret-right text-info"></i> Set the pricing to the minimum price i.e. 1 USD</li>
<li lang="en"><i class="fa fa-caret-right text-info"></i> Click <code>Save Product</code></li>
<li lang="en"><i class="fa fa-caret-right text-info"></i> Click The Product you just created</li>
<li lang="en"><i class="fa fa-caret-right text-info"></i> Copy <code>ID</code> value</li>
<li lang="en"><i class="fa fa-caret-right text-info"></i> Click <code>Developers</code></li>
<li lang="en"><i class="fa fa-caret-right text-info"></i> Click <code>API Keys</code></li>
<li lang="en"><i class="fa fa-caret-right text-info"></i> Copy both <code>Publishable key</code> and <code>Secret key</code></li>
</ul>
</div>
</div>
</div>
']
),
],
'Enable' => [
$this->settingsOption('enable', 'homepageDonateEnabled'),
$this->settingsOption('auth', 'homepageDonateAuth'),
],
'Connection' => [
$this->settingsOption('input', 'homepageDonatePublicToken', ['label' => 'Public Token']),
$this->settingsOption('token', 'homepageDonateSecretToken', ['label' => 'Secret Token']),
$this->settingsOption('input', 'homepageDonateProductID', ['label' => 'Product ID']),
],
'Customize' => [
$this->settingsOption('input', 'homepageDonateCustomizeHeading', ['label' => 'Heading']),
$this->settingsOption('code-editor', 'homepageDonateCustomizeDescription', ['label' => 'Description', 'mode' => 'html']),
$this->settingsOption('select', 'homepageDonateMinimum',
['label' => 'Minimum',
'options' => [
['name' => '1 USD', 'value' => '100'],
['name' => '2 USD', 'value' => '200'],
['name' => '3 USD', 'value' => '300'],
['name' => '4 USD', 'value' => '400'],
['name' => '5 USD', 'value' => '500'],
['name' => '10 USD', 'value' => '1000'],
['name' => '20 USD', 'value' => '2000'],
['name' => '25 USD', 'value' => '2500'],
['name' => '50 USD', 'value' => '5000'],
['name' => '75 USD', 'value' => '7500'],
['name' => '100 USD', 'value' => '10000'],
]
]),
$this->settingsOption('switch', 'homepageDonateShowUserHistory', ['label' => 'Show User Donate History']),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function donateHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageDonateEnabled'
],
'auth' => [
'homepageDonateAuth'
],
'not_empty' => [
'homepageDonateMinimum',
'homepageDonatePublicToken',
'homepageDonateSecretToken',
'homepageDonateProductID',
]
],
'history' => [
'enabled' => [
'homepageDonateEnabled',
'homepageDonateShowUserHistory'
],
'auth' => [
'homepageDonateAuth'
],
'not_empty' => [
'homepageDonateMinimum',
'homepageDonatePublicToken',
'homepageDonateSecretToken',
'homepageDonateProductID',
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageDonateUserHistory()
{
$items = [];
if ($this->homepageItemPermissions($this->donateHomepagePermissions('history'))) {
try {
$stripe = new \Stripe\StripeClient(
trim($this->config['homepageDonateSecretToken'])
);
$history = $stripe->charges->all(['limit' => 100]);
if (count($history) > 0) {
if ($this->user['email']) {
foreach ($history as $charge) {
if (($this->qualifyRequest(0) || (strtolower($charge['billing_details']['email']) == strtolower($this->user['email']))) && $charge['amount_captured'] > 0) {
$items[] = [
'date' => date('Y-m-d\TH:i:s\Z', $charge['created']),
'email' => $charge['billing_details']['email'],
'amount' => $charge['amount_captured'] / 100
];
}
}
}
}
} catch (\Stripe\Exception\ApiErrorException $e) {
die($this->showHTML('Error', $e->getMessage()));
}
}
$this->setResponse(200, null, $items);
return $items;
}
public function homepageDonateCreateSession($amount = null)
{
$amount = $amount ? $amount * 100 : $this->config['homepageDonateMinimum'];
if ($this->config['homepageDonatePublicToken'] == '' || $this->config['homepageDonateSecretToken'] == '' || $this->config['homepageDonateProductID'] == '') {
$this->setResponse(409, 'Donation Tokens are not setup');
return false;
}
try {
$stripe = new \Stripe\StripeClient(
trim($this->config['homepageDonateSecretToken'])
);
$sessionInfo = [
'payment_method_types' => ['card'],
'line_items' => [[
'price_data' => [
'product' => $this->config['homepageDonateProductID'],
'unit_amount' => $amount,
'currency' => 'usd',
],
'quantity' => 1,
]],
'mode' => 'payment',
'success_url' => $this->getServerPath() . 'api/v2/homepage/donate/success',
'cancel_url' => $this->getServerPath() . 'api/v2/homepage/donate/cancel',
];
if ($this->user['email'] && stripos($this->user['email'], 'placeholder') == false) {
$sessionInfo = array_merge($sessionInfo, ['customer_email' => $this->user['email']]);
}
$session = $stripe->checkout->sessions->create($sessionInfo);
header('HTTP/1.1 303 See Other');
header('Location: ' . $session->url);
} catch (\Stripe\Exception\ApiErrorException $e) {
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function homepageOrderDonate()
{
if ($this->homepageItemPermissions($this->donateHomepagePermissions('main'))) {
$minimum = $this->config['homepageDonateMinimum'] / 100;
$history = $this->config['homepageDonateShowUserHistory'] ? '<div class="pull-right"><a href="javascript:void(0)" class="toggle-donation-history" data-status="hidden"><i class="fa fa-clock-o"></i></a> </div>' : '';
return '
<script>
$(document).on("keyup", "#custom-donation-amount", function () {
$("#homepage-donation-form").attr("action", "api/v2/homepage/donate?amount=" + $(this).val());
});
</script>
<div id="' . __FUNCTION__ . '">
<div class="panel panel-primary" style="position: static; zoom: 1;">
<div class="panel-heading"> ' . $this->config['homepageDonateCustomizeHeading'] . $history . '</div>
<div class="panel-wrapper collapse in" aria-expanded="true">
<div class="panel-body">
<p>' . $this->config['homepageDonateCustomizeDescription'] . '</p>
<script src="https://polyfill.io/v3/polyfill.min.js?version=3.52.1&features=fetch"></script>
<script src="https://js.stripe.com/v3/"></script>
<form id="homepage-donation-form" action="api/v2/homepage/donate?amount=' . $minimum . '" method="POST" target="_blank">
<div class="input-group m-b-30">
<span class="input-group-addon">$</span>
<input type="number" class="form-control" name="amount" id="custom-donation-amount" placeholder="' . $minimum . '" min="' . $minimum . '"/>
<span class="input-group-btn">
<button class="btn btn-info" type="submit" id="checkout-button" lang="en">Donate</button>
</span>
</div>
</form>
<div class="donation-history hidden"></div>
</div>
</div>
</div>
</div>
';
}
}
}

View File

@@ -0,0 +1,523 @@
<?php
trait EmbyHomepageItem
{
public function embySettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Emby',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/emby.png',
'category' => 'Media Server',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageEmbyEnabled'),
$this->settingsOption('auth', 'homepageEmbyAuth'),
],
'Connection' => [
$this->settingsOption('url', 'embyURL'),
$this->settingsOption('token', 'embyToken'),
$this->settingsOption('disable-cert-check', 'embyDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'embyUseCustomCertificate'),
],
'Active Streams' => [
$this->settingsOption('enable', 'homepageEmbyStreams'),
$this->settingsOption('auth', 'homepageEmbyStreamsAuth'),
$this->settingsOption('switch', 'homepageShowStreamNames', ['label' => 'User Information']),
$this->settingsOption('auth', 'homepageShowStreamNamesAuth'),
$this->settingsOption('refresh', 'homepageStreamRefresh'),
],
'Recent Items' => [
$this->settingsOption('enable', 'homepageEmbyRecent'),
$this->settingsOption('auth', 'homepageEmbyRecentAuth'),
$this->settingsOption('limit', 'homepageRecentLimit'),
$this->settingsOption('refresh', 'homepageRecentRefresh'),
],
'Misc Options' => [
$this->settingsOption('input', 'homepageEmbyLink', ['label' => 'Emby Homepage Link URL', 'help' => 'Available variables: {id} {serverId}']),
$this->settingsOption('input', 'embyTabName', ['label' => 'Emby Tab Name', 'placeholder' => 'Only use if you have Emby in a reverse proxy']),
$this->settingsOption('input', 'embyTabURL', ['label' => 'Emby Tab WAN URL', 'placeholder' => 'Only use if you have Emby in a reverse proxy']),
$this->settingsOption('image-cache-quality', 'cacheImageSize'),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'emby'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionEmby()
{
if (!$this->homepageItemPermissions($this->embyHomepagePermissions('test'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['embyURL']);
$url = $url . "/Users?api_key=" . $this->config['embyToken'];
$options = $this->requestOptions($url, null, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
try {
$response = Requests::get($url, [], $options);
if ($response->success) {
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
} else {
$this->setAPIResponse('error', 'Emby Connection Error', 500);
return true;
}
} catch (Requests_Exception $e) {
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function embyHomepagePermissions($key = null)
{
$permissions = [
'test' => [
'enabled' => [
'homepageEmbyEnabled',
],
'auth' => [
'homepageEmbyAuth',
],
'not_empty' => [
'embyURL',
'embyToken'
]
],
'streams' => [
'enabled' => [
'homepageEmbyEnabled',
'homepageEmbyStreams'
],
'auth' => [
'homepageEmbyAuth',
'homepageEmbyStreamsAuth'
],
'not_empty' => [
'embyURL',
'embyToken'
]
],
'recent' => [
'enabled' => [
'homepageEmbyEnabled',
'homepageEmbyRecent'
],
'auth' => [
'homepageEmbyAuth',
'homepageEmbyRecentAuth'
],
'not_empty' => [
'embyURL',
'embyToken'
]
],
'metadata' => [
'enabled' => [
'homepageEmbyEnabled'
],
'auth' => [
'homepageEmbyAuth'
],
'not_empty' => [
'embyURL',
'embyToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderembynowplaying()
{
if ($this->homepageItemPermissions($this->embyHomepagePermissions('streams'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Now Playing...</h2></div>
<script>
// Emby Stream
homepageStream("emby", "' . $this->config['homepageStreamRefresh'] . '");
// End Emby Stream
</script>
</div>
';
}
}
public function homepageOrderembyrecent()
{
if ($this->homepageItemPermissions($this->embyHomepagePermissions('recent'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Recent...</h2></div>
<script>
// Emby Recent
homepageRecent("emby", "' . $this->config['homepageRecentRefresh'] . '");
// End Emby Recent
</script>
</div>
';
}
}
public function getEmbyHomepageStreams()
{
if (!$this->homepageItemPermissions($this->embyHomepagePermissions('streams'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['embyURL']);
$url = $url . '/Sessions?api_key=' . $this->config['embyToken'] . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines';
$options = $this->requestOptions($url, $this->config['homepageStreamRefresh'], $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
try {
$response = Requests::get($url, [], $options);
if ($response->success) {
$items = array();
$emby = json_decode($response->body, true);
foreach ($emby as $child) {
if (isset($child['NowPlayingItem']) || isset($child['Name'])) {
$items[] = $this->resolveEmbyItem($child);
}
}
$api['content'] = array_filter($items);
$this->setAPIResponse('success', null, 200, $api);
return $api;
} else {
$this->setAPIResponse('error', 'Emby Error Occurred', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Emby')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function getEmbyHomepageRecent()
{
if (!$this->homepageItemPermissions($this->embyHomepagePermissions('recent'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['embyURL']);
$options = $this->requestOptions($url, $this->config['homepageRecentRefresh'], $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
$username = false;
$showPlayed = false;
$userId = 0;
try {
if (isset($this->user['username'])) {
$username = strtolower($this->user['username']);
}
// Get A User
$userIds = $url . "/Users?api_key=" . $this->config['embyToken'];
$response = Requests::get($userIds, [], $options);
if ($response->success) {
$emby = json_decode($response->body, true);
foreach ($emby as $value) { // Scan for admin user
if (isset($value['Policy']) && isset($value['Policy']['IsAdministrator']) && $value['Policy']['IsAdministrator']) {
$userId = $value['Id'];
}
if ($username && strtolower($value['Name']) == $username) {
$userId = $value['Id'];
$showPlayed = false;
break;
}
}
$url = $url . '/Users/' . $userId . '/Items/Latest?EnableImages=true&Limit=' . $this->config['homepageRecentLimit'] . '&api_key=' . $this->config['embyToken'] . ($showPlayed ? '' : '&IsPlayed=false') . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines&IncludeItemTypes=Series,Episode,MusicAlbum,Audio,Movie,Video';
} else {
$this->setAPIResponse('error', 'Emby Error Occurred', 500);
return false;
}
$response = Requests::get($url, [], $options);
if ($response->success) {
$items = array();
$emby = json_decode($response->body, true);
foreach ($emby as $child) {
if (isset($child['NowPlayingItem']) || isset($child['Name'])) {
$items[] = $this->resolveEmbyItem($child);
}
}
$api['content'] = array_filter($items);
$this->setAPIResponse('success', null, 200, $api);
return $api;
} else {
$this->setAPIResponse('error', 'Emby Error Occurred', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Emby')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function getEmbyHomepageMetadata($array)
{
if (!$this->homepageItemPermissions($this->embyHomepagePermissions('metadata'), true)) {
return false;
}
$key = $array['key'] ?? null;
if (!$key) {
$this->setAPIResponse('error', 'Emby Metadata key is not defined', 422);
return false;
}
$url = $this->qualifyURL($this->config['embyURL']);
$options = $this->requestOptions($url, 60, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
$username = false;
$showPlayed = false;
$userId = 0;
try {
if (isset($this->user['username'])) {
$username = strtolower($this->user['username']);
}
// Get A User
$userIds = $url . "/Users?api_key=" . $this->config['embyToken'];
$response = Requests::get($userIds, [], $options);
if ($response->success) {
$emby = json_decode($response->body, true);
foreach ($emby as $value) { // Scan for admin user
if (isset($value['Policy']) && isset($value['Policy']['IsAdministrator']) && $value['Policy']['IsAdministrator']) {
$userId = $value['Id'];
}
if ($username && strtolower($value['Name']) == $username) {
$userId = $value['Id'];
$showPlayed = false;
break;
}
}
$url = $url . '/Users/' . $userId . '/Items/' . $key . '?EnableImages=true&Limit=' . $this->config['homepageRecentLimit'] . '&api_key=' . $this->config['embyToken'] . ($showPlayed ? '' : '&IsPlayed=false') . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines';
} else {
$this->setAPIResponse('error', 'Emby Error Occurred', 500);
return false;
}
$response = Requests::get($url, [], $options);
if ($response->success) {
$items = array();
$emby = json_decode($response->body, true);
if (isset($emby['NowPlayingItem']) || isset($emby['Name'])) {
$items[] = $this->resolveEmbyItem($emby);
}
$api['content'] = array_filter($items);
$this->setAPIResponse('success', null, 200, $api);
return $api;
} else {
$this->setAPIResponse('error', 'Emby Error Occurred', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Emby')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function resolveEmbyItem($itemDetails)
{
$item = isset($itemDetails['NowPlayingItem']['Id']) ? $itemDetails['NowPlayingItem'] : $itemDetails;
// Static Height & Width
$height = $this->getCacheImageSize('h');
$width = $this->getCacheImageSize('w');
$nowPlayingHeight = $this->getCacheImageSize('nph');
$nowPlayingWidth = $this->getCacheImageSize('npw');
$actorHeight = 450;
$actorWidth = 300;
// Cache Directories
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$cacheDirectoryWeb = 'data/cache/';
// Types
//$embyItem['array-item'] = $item;
//$embyItem['array-itemdetails'] = $itemDetails;
switch (@$item['Type']) {
case 'Series':
$embyItem['type'] = 'tv';
$embyItem['title'] = $item['Name'];
$embyItem['secondaryTitle'] = '';
$embyItem['summary'] = '';
$embyItem['ratingKey'] = $item['Id'];
$embyItem['thumb'] = $item['Id'];
$embyItem['key'] = $item['Id'] . "-list";
$embyItem['nowPlayingThumb'] = $item['Id'];
$embyItem['nowPlayingKey'] = $item['Id'] . "-np";
$embyItem['metadataKey'] = $item['Id'];
$embyItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? 'Thumb' : (isset($item['BackdropImageTags'][0]) ? 'Backdrop' : '');
break;
case 'Episode':
$embyItem['type'] = 'tv';
$embyItem['title'] = $item['SeriesName'];
$embyItem['secondaryTitle'] = '';
$embyItem['summary'] = '';
$embyItem['ratingKey'] = $item['Id'];
$embyItem['thumb'] = (isset($item['SeriesId']) ? $item['SeriesId'] : $item['Id']);
$embyItem['key'] = (isset($item['SeriesId']) ? $item['SeriesId'] : $item['Id']) . "-list";
$embyItem['nowPlayingThumb'] = isset($item['ParentThumbItemId']) ? $item['ParentThumbItemId'] : (isset($item['ParentBackdropItemId']) ? $item['ParentBackdropItemId'] : false);
$embyItem['nowPlayingKey'] = isset($item['ParentThumbItemId']) ? $item['ParentThumbItemId'] . '-np' : (isset($item['ParentBackdropItemId']) ? $item['ParentBackdropItemId'] . '-np' : false);
$embyItem['metadataKey'] = $item['Id'];
$embyItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? 'Thumb' : (isset($item['ParentBackdropImageTags'][0]) ? 'Backdrop' : '');
$embyItem['nowPlayingTitle'] = @$item['SeriesName'] . ' - ' . @$item['Name'];
$embyItem['nowPlayingBottom'] = 'S' . @$item['ParentIndexNumber'] . ' · E' . @$item['IndexNumber'];
break;
case 'MusicAlbum':
case 'Audio':
$embyItem['type'] = 'music';
$embyItem['title'] = $item['Name'];
$embyItem['secondaryTitle'] = '';
$embyItem['summary'] = '';
$embyItem['ratingKey'] = $item['Id'];
$embyItem['thumb'] = $item['Id'];
$embyItem['key'] = $item['Id'] . "-list";
$embyItem['nowPlayingThumb'] = (isset($item['AlbumId']) ? $item['AlbumId'] : @$item['ParentBackdropItemId']);
$embyItem['nowPlayingKey'] = $item['Id'] . "-np";
$embyItem['metadataKey'] = isset($item['AlbumId']) ? $item['AlbumId'] : $item['Id'];
$embyItem['nowPlayingImageType'] = (isset($item['ParentBackdropItemId']) ? "Primary" : "Backdrop");
$embyItem['nowPlayingTitle'] = @$item['AlbumArtist'] . ' - ' . @$item['Name'];
$embyItem['nowPlayingBottom'] = @$item['Album'];
break;
case 'Movie':
$embyItem['type'] = 'movie';
$embyItem['title'] = $item['Name'];
$embyItem['secondaryTitle'] = '';
$embyItem['summary'] = '';
$embyItem['ratingKey'] = $item['Id'];
$embyItem['thumb'] = $item['Id'];
$embyItem['key'] = $item['Id'] . "-list";
$embyItem['nowPlayingThumb'] = $item['Id'];
$embyItem['nowPlayingKey'] = $item['Id'] . "-np";
$embyItem['metadataKey'] = $item['Id'];
$embyItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? "Thumb" : (isset($item['BackdropImageTags']) ? "Backdrop" : false);
$embyItem['nowPlayingTitle'] = @$item['Name'];
$embyItem['nowPlayingBottom'] = @$item['ProductionYear'];
break;
case 'Video':
$embyItem['type'] = 'video';
$embyItem['title'] = $item['Name'];
$embyItem['secondaryTitle'] = '';
$embyItem['summary'] = '';
$embyItem['ratingKey'] = $item['Id'];
$embyItem['thumb'] = $item['Id'];
$embyItem['key'] = $item['Id'] . "-list";
$embyItem['nowPlayingThumb'] = $item['Id'];
$embyItem['nowPlayingKey'] = $item['Id'] . "-np";
$embyItem['metadataKey'] = $item['Id'];
$embyItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? "Thumb" : (isset($item['BackdropImageTags']) ? "Backdrop" : false);
$embyItem['nowPlayingTitle'] = @$item['Name'];
$embyItem['nowPlayingBottom'] = @$item['ProductionYear'];
break;
default:
return false;
}
$embyItem['uid'] = $item['Id'];
$embyItem['imageType'] = (isset($item['ImageTags']['Primary']) ? "Primary" : false);
$embyItem['elapsed'] = isset($itemDetails['PlayState']['PositionTicks']) && $itemDetails['PlayState']['PositionTicks'] !== '0' ? (int)$itemDetails['PlayState']['PositionTicks'] : null;
$embyItem['duration'] = isset($itemDetails['NowPlayingItem']['RunTimeTicks']) ? (int)$itemDetails['NowPlayingItem']['RunTimeTicks'] : (int)(isset($item['RunTimeTicks']) ? $item['RunTimeTicks'] : '');
$embyItem['watched'] = ($embyItem['elapsed'] && $embyItem['duration'] ? floor(($embyItem['elapsed'] / $embyItem['duration']) * 100) : 0);
$embyItem['transcoded'] = isset($itemDetails['TranscodingInfo']['CompletionPercentage']) ? floor((int)$itemDetails['TranscodingInfo']['CompletionPercentage']) : 100;
$embyItem['stream'] = @$itemDetails['PlayState']['PlayMethod'];
$embyItem['id'] = $item['ServerId'];
$embyItem['session'] = @$itemDetails['DeviceId'];
$embyItem['bandwidth'] = isset($itemDetails['TranscodingInfo']['Bitrate']) ? $itemDetails['TranscodingInfo']['Bitrate'] / 1000 : '';
$embyItem['bandwidthType'] = 'wan';
$embyItem['sessionType'] = (@$itemDetails['PlayState']['PlayMethod'] == 'Transcode') ? 'Transcoding' : 'Direct Playing';
$embyItem['state'] = ((@(string)$itemDetails['PlayState']['IsPaused'] == '1') ? "pause" : "play");
$embyItem['user'] = ($this->config['homepageShowStreamNames'] && $this->qualifyRequest($this->config['homepageShowStreamNamesAuth'])) ? @(string)$itemDetails['UserName'] : "";
$embyItem['userThumb'] = '';
$embyItem['userAddress'] = (isset($itemDetails['RemoteEndPoint']) ? $itemDetails['RemoteEndPoint'] : "x.x.x.x");
$embyVariablesForLink = [
'{id}' => $embyItem['uid'],
'{serverId}' => $embyItem['id']
];
$embyItem['address'] = $this->userDefinedIdReplacementLink($this->config['homepageEmbyLink'], $embyVariablesForLink);
$embyItem['nowPlayingOriginalImage'] = 'api/v2/homepage/image?source=emby&type=' . $embyItem['nowPlayingImageType'] . '&img=' . $embyItem['nowPlayingThumb'] . '&height=' . $nowPlayingHeight . '&width=' . $nowPlayingWidth . '&key=' . $embyItem['nowPlayingKey'] . '$' . $this->randString();
$embyItem['originalImage'] = 'api/v2/homepage/image?source=emby&type=' . $embyItem['imageType'] . '&img=' . $embyItem['thumb'] . '&height=' . $height . '&width=' . $width . '&key=' . $embyItem['key'] . '$' . $this->randString();
$embyItem['openTab'] = $this->config['embyTabURL'] && $this->config['embyTabName'] ? true : false;
$embyItem['tabName'] = $this->config['embyTabName'] ? $this->config['embyTabName'] : '';
// Stream info
$embyItem['userStream'] = array(
'platform' => @(string)$itemDetails['Client'],
'product' => @(string)$itemDetails['Client'],
'device' => @(string)$itemDetails['DeviceName'],
'stream' => @$itemDetails['PlayState']['PlayMethod'],
'videoResolution' => isset($itemDetails['NowPlayingItem']['MediaStreams'][0]['Width']) ? $itemDetails['NowPlayingItem']['MediaStreams'][0]['Width'] : '',
'throttled' => false,
'sourceVideoCodec' => isset($itemDetails['NowPlayingItem']['MediaStreams'][0]) ? $itemDetails['NowPlayingItem']['MediaStreams'][0]['Codec'] : '',
'videoCodec' => @$itemDetails['TranscodingInfo']['VideoCodec'],
'audioCodec' => @$itemDetails['TranscodingInfo']['AudioCodec'],
'sourceAudioCodec' => isset($itemDetails['NowPlayingItem']['MediaStreams'][1]) ? $itemDetails['NowPlayingItem']['MediaStreams'][1]['Codec'] : (isset($itemDetails['NowPlayingItem']['MediaStreams'][0]) ? $itemDetails['NowPlayingItem']['MediaStreams'][0]['Codec'] : ''),
'videoDecision' => $this->streamType(@$itemDetails['PlayState']['PlayMethod']),
'audioDecision' => $this->streamType(@$itemDetails['PlayState']['PlayMethod']),
'container' => isset($itemDetails['NowPlayingItem']['Container']) ? $itemDetails['NowPlayingItem']['Container'] : '',
'audioChannels' => @$itemDetails['TranscodingInfo']['AudioChannels']
);
// Genre catch all
if (isset($item['Genres'])) {
$genres = array();
foreach ($item['Genres'] as $genre) {
$genres[] = $genre;
}
}
// Actor catch all
if (isset($item['People'])) {
$actors = array();
foreach ($item['People'] as $key => $value) {
if (@$value['PrimaryImageTag'] && @$value['Role']) {
if (file_exists($cacheDirectory . (string)$value['Id'] . '-cast.jpg')) {
$actorImage = $cacheDirectoryWeb . (string)$value['Id'] . '-cast.jpg';
}
if (file_exists($cacheDirectory . (string)$value['Id'] . '-cast.jpg') && (time() - 604800) > filemtime($cacheDirectory . (string)$value['Id'] . '-cast.jpg') || !file_exists($cacheDirectory . (string)$value['Id'] . '-cast.jpg')) {
$actorImage = 'api/v2/homepage/image?source=emby&type=Primary&img=' . (string)$value['Id'] . '&height=' . $actorHeight . '&width=' . $actorWidth . '&key=' . (string)$value['Id'] . '-cast';
}
$actors[] = array(
'name' => (string)$value['Name'],
'role' => (string)$value['Role'],
'thumb' => $actorImage
);
}
}
}
// Metadata information
$embyItem['metadata'] = array(
'guid' => $item['Id'],
'summary' => @(string)$item['Overview'],
'rating' => @(string)$item['CommunityRating'],
'duration' => @(string)$item['RunTimeTicks'],
'originallyAvailableAt' => @(string)$item['PremiereDate'],
'year' => (string)isset($item['ProductionYear']) ? $item['ProductionYear'] : '',
//'studio' => (string)$item['studio'],
'tagline' => @(string)$item['Taglines'][0],
'genres' => (isset($item['Genres'])) ? $genres : '',
'actors' => (isset($item['People'])) ? $actors : ''
);
if (file_exists($cacheDirectory . $embyItem['nowPlayingKey'] . '.jpg')) {
$embyItem['nowPlayingImageURL'] = $cacheDirectoryWeb . $embyItem['nowPlayingKey'] . '.jpg';
}
if (file_exists($cacheDirectory . $embyItem['key'] . '.jpg')) {
$embyItem['imageURL'] = $cacheDirectoryWeb . $embyItem['key'] . '.jpg';
}
if (file_exists($cacheDirectory . $embyItem['nowPlayingKey'] . '.jpg') && (time() - 604800) > filemtime($cacheDirectory . $embyItem['nowPlayingKey'] . '.jpg') || !file_exists($cacheDirectory . $embyItem['nowPlayingKey'] . '.jpg')) {
$embyItem['nowPlayingImageURL'] = 'api/v2/homepage/image?source=emby&type=' . $embyItem['nowPlayingImageType'] . '&img=' . $embyItem['nowPlayingThumb'] . '&height=' . $nowPlayingHeight . '&width=' . $nowPlayingWidth . '&key=' . $embyItem['nowPlayingKey'] . '';
}
if (file_exists($cacheDirectory . $embyItem['key'] . '.jpg') && (time() - 604800) > filemtime($cacheDirectory . $embyItem['key'] . '.jpg') || !file_exists($cacheDirectory . $embyItem['key'] . '.jpg')) {
$embyItem['imageURL'] = 'api/v2/homepage/image?source=emby&type=' . $embyItem['imageType'] . '&img=' . $embyItem['thumb'] . '&height=' . $height . '&width=' . $width . '&key=' . $embyItem['key'] . '';
}
if (!$embyItem['nowPlayingThumb']) {
$embyItem['nowPlayingOriginalImage'] = $embyItem['nowPlayingImageURL'] = "plugins/images/homepage/no-np.png";
$embyItem['nowPlayingKey'] = "no-np";
}
if (!$embyItem['thumb']) {
$embyItem['originalImage'] = $embyItem['imageURL'] = "plugins/images/homepage/no-list.png";
$embyItem['key'] = "no-list";
}
if (isset($useImage)) {
$embyItem['useImage'] = $useImage;
}
return $embyItem;
}
}

View File

@@ -0,0 +1,801 @@
<?php
trait EmbyLiveTVTrackerHomepageItem
{
public function embyLiveTVTrackerSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'EmbyLiveTVTracker',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/homepage/embyLiveTVTracker.png',
'category' => 'Media Server',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageEmbyLiveTVTrackerEnabled'),
$this->settingsOption('auth', 'homepageEmbyLiveTVTrackerAuth'),
],
'Connection' => [
$this->settingsOption('url', 'embyURL'),
$this->settingsOption('token', 'embyToken'),
$this->settingsOption('disable-cert-check', 'embyDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'embyUseCustomCertificate'),
],
'Display Options' => [
$this->settingsOption('number', 'homepageEmbyLiveTVTrackerRefresh', ['label' => 'Auto-refresh Interval (minutes)', 'min' => 1, 'max' => 60]),
$this->settingsOption('switch', 'homepageEmbyLiveTVTrackerCompactView', ['label' => 'Use Compact View']),
$this->settingsOption('switch', 'homepageEmbyLiveTVTrackerShowDuration', ['label' => 'Show Recording Duration']),
$this->settingsOption('switch', 'homepageEmbyLiveTVTrackerShowSeriesInfo', ['label' => 'Show Series Information']),
$this->settingsOption('switch', 'homepageEmbyLiveTVTrackerShowUserInfo', ['label' => 'Show User Information']),
$this->settingsOption('number', 'homepageEmbyLiveTVTrackerMaxItems', ['label' => 'Maximum Scheduled Items', 'min' => 5, 'max' => 50]),
$this->settingsOption('switch', 'homepageEmbyLiveTVTrackerShowCompleted', ['label' => 'Show Completed Recordings']),
$this->settingsOption('number', 'homepageEmbyLiveTVTrackerDaysShown', ['label' => 'Days of Completed Recordings', 'min' => 1, 'max' => 30]),
$this->settingsOption('number', 'homepageEmbyLiveTVTrackerMaxCompletedItems', ['label' => 'Maximum Completed Items', 'min' => 5, 'max' => 50]),
$this->settingsOption('switch', 'homepageEmbyLiveTVTrackerDebug', ['label' => 'Enable Debug Logging']),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'embyLiveTVTracker'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionEmbyLiveTVTracker()
{
if (!$this->homepageItemPermissions($this->embyLiveTVTrackerHomepagePermissions('test'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['embyURL']);
$url = $url . "/emby/System/Info?api_key=" . $this->config['embyToken'];
$options = $this->requestOptions($url, null, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
try {
$response = Requests::get($url, [], $options);
if ($response->success) {
$info = json_decode($response->body, true);
if (isset($info['ServerName'])) {
// Test LiveTV functionality
$liveTvUrl = $this->qualifyURL($this->config['embyURL']) . '/emby/LiveTv/Info?api_key=' . $this->config['embyToken'];
try {
$liveTvResponse = Requests::get($liveTvUrl, [], $options);
$liveTvInfo = json_decode($liveTvResponse->body, true);
$hasLiveTV = isset($liveTvInfo['Services']) && count($liveTvInfo['Services']) > 0;
$message = 'Successfully connected to ' . $info['ServerName'];
if ($hasLiveTV) {
$message .= ' with LiveTV support enabled';
} else {
$message .= ' (Warning: LiveTV may not be configured)';
}
$this->setAPIResponse('success', $message, 200);
} catch (Exception $e) {
$this->setAPIResponse('success', 'Connected to ' . $info['ServerName'] . ' but LiveTV status unknown', 200);
}
} else {
$this->setAPIResponse('error', 'Invalid response from Emby server', 500);
}
return true;
} else {
$this->setAPIResponse('error', 'Emby Connection Error', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function embyLiveTVTrackerHomepagePermissions($key = null)
{
$permissions = [
'test' => [
'enabled' => [
'homepageEmbyLiveTVTrackerEnabled',
],
'auth' => [
'homepageEmbyLiveTVTrackerAuth',
],
'not_empty' => [
'embyURL',
'embyToken'
]
],
'main' => [
'enabled' => [
'homepageEmbyLiveTVTrackerEnabled'
],
'auth' => [
'homepageEmbyLiveTVTrackerAuth'
],
'not_empty' => [
'embyURL',
'embyToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderEmbyLiveTVTracker()
{
if ($this->homepageItemPermissions($this->embyLiveTVTrackerHomepagePermissions('main'))) {
$refreshInterval = ($this->config['homepageEmbyLiveTVTrackerRefresh'] ?? 5) * 60000; // Convert minutes to milliseconds
$compactView = ($this->config['homepageEmbyLiveTVTrackerCompactView'] ?? false) ? 'true' : 'false';
$showDuration = ($this->config['homepageEmbyLiveTVTrackerShowDuration'] ?? true) ? 'true' : 'false';
$showSeriesInfo = ($this->config['homepageEmbyLiveTVTrackerShowSeriesInfo'] ?? true) ? 'true' : 'false';
$showUserInfo = ($this->config['homepageEmbyLiveTVTrackerShowUserInfo'] ?? false) ? 'true' : 'false';
$maxItems = $this->config['homepageEmbyLiveTVTrackerMaxItems'] ?? 10;
$showCompleted = ($this->config['homepageEmbyLiveTVTrackerShowCompleted'] ?? true) ? 'true' : 'false';
$daysShown = $this->config['homepageEmbyLiveTVTrackerDaysShown'] ?? 7;
$maxCompletedItems = $this->config['homepageEmbyLiveTVTrackerMaxCompletedItems'] ?? 5;
$panelClass = ($compactView === 'true') ? 'panel-compact' : '';
$statsClass = ($compactView === 'true') ? 'col-sm-6' : 'col-sm-3';
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box ' . $panelClass . '">
<div class="white-box-header">
<i class="fa fa-tv"></i> Emby LiveTV Tracker
<span class="pull-right">
<small id="embylivetv-last-update" class="text-muted"></small>
<button class="btn btn-xs btn-primary" onclick="refreshEmbyLiveTVData()" title="Refresh Data">
<i class="fa fa-refresh" id="embylivetv-refresh-icon"></i>
</button>
</span>
</div>
<div class="white-box-content">
<div class="row" id="embylivetv-stats">
<div class="' . $statsClass . '">
<div class="text-center">
<h3 id="embylivetv-active-timers" class="text-success">-</h3>
<small>Active Timers</small>
</div>
</div>
<div class="' . $statsClass . '">
<div class="text-center">
<h3 id="embylivetv-series-timers" class="text-info">-</h3>
<small>Series Timers</small>
</div>
</div>
' . (($compactView === 'false') ? '
<div class="' . $statsClass . '">
<div class="text-center">
<h3 id="embylivetv-today-recordings" class="text-warning">-</h3>
<small>Today\'s Recordings</small>
</div>
</div>
<div class="' . $statsClass . '">
<div class="text-center">
<h3 id="embylivetv-total-recordings" class="text-primary">-</h3>
<small>Total Recordings</small>
</div>
</div>
' : '') . '
</div>
<!-- Scheduled Recordings Table -->
<div class="row" style="margin-top: 20px;">
<div class="col-lg-12">
<h4>
Scheduled Recordings
<small class="text-muted">Upcoming and active timers</small>
</h4>
<div class="table-responsive">
<table class="table table-hover table-striped table-condensed">
<thead>
<tr>
<th width="120">Date</th>
<th>Series</th>
' . (($showSeriesInfo === 'true') ? '<th>Episode Title</th>' : '') . '
<th>Channel</th>
' . (($showUserInfo === 'true') ? '<th>User</th>' : '') . '
' . (($showDuration === 'true') ? '<th width="80">Duration</th>' : '') . '
<th width="70">Status</th>
</tr>
</thead>
<tbody id="embylivetv-scheduled-table">
<tr>
<td colspan="' . (4 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showUserInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '" class="text-center">
<i class="fa fa-spinner fa-spin"></i> Loading...
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Completed Recordings Table (only if enabled) -->
' . (($showCompleted === 'true') ? '
<div class="row" style="margin-top: 20px;">
<div class="col-lg-12">
<h4>
Completed Recordings
<small class="text-muted">Recent recordings</small>
</h4>
<div class="table-responsive">
<table class="table table-hover table-striped table-condensed">
<thead>
<tr>
<th width="120">Date</th>
<th>Series</th>
' . (($showSeriesInfo === 'true') ? '<th>Series</th>' : '') . '
' . (($showDuration === 'true') ? '<th width="80">Duration</th>' : '') . '
<th width="70">Status</th>
</tr>
</thead>
<tbody id="embylivetv-completed-table">
<tr>
<td colspan="' . (3 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '" class="text-center">
<i class="fa fa-spinner fa-spin"></i> Loading...
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
' : '') . '
</div>
</div>
</div>
<style>
.panel-compact .white-box-content { padding: 10px; }
.panel-compact h3 { margin: 5px 0; font-size: 1.8em; }
.panel-compact small { font-size: 0.85em; }
#' . __FUNCTION__ . ' .table-condensed td { padding: 4px 8px; font-size: 0.9em; }
#' . __FUNCTION__ . ' .status-success { color: #5cb85c; }
#' . __FUNCTION__ . ' .status-recording { color: #d9534f; }
#' . __FUNCTION__ . ' .status-scheduled { color: #f0ad4e; }
</style>
<script>
var embyLiveTVRefreshTimer;
var embyLiveTVLastRefresh = 0;
function refreshEmbyLiveTVData() {
var refreshIcon = $("#embylivetv-refresh-icon");
refreshIcon.addClass("fa-spin");
// Show loading state
$("#embylivetv-stats h3").text("-");
$("#embylivetv-scheduled-table").html("<tr><td colspan=\"' . (4 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showUserInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '\" class=\"text-center\"><i class=\"fa fa-spinner fa-spin\"></i> Loading...</td></tr>");
' . (($showCompleted === 'true') ? '$("#embylivetv-completed-table").html("<tr><td colspan=\"' . (4 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showUserInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '\" class=\"text-center\"><i class=\"fa fa-spinner fa-spin\"></i> Loading...</td></tr>");' : '') . ';
// Load stats and activity
homepageEmbyLiveTVTrackerStats()
.always(function() {
refreshIcon.removeClass("fa-spin");
embyLiveTVLastRefresh = Date.now();
updateEmbyLiveTVLastRefreshTime();
});
}
function updateEmbyLiveTVLastRefreshTime() {
if (embyLiveTVLastRefresh > 0) {
var ago = Math.floor((Date.now() - embyLiveTVLastRefresh) / 1000);
var timeText = ago < 60 ? ago + "s ago" : Math.floor(ago / 60) + "m ago";
$("#embylivetv-last-update").text("Updated " + timeText);
}
}
function homepageEmbyLiveTVTrackerStats() {
return organizrAPI2("GET", "api/v2/homepage/embyLiveTVTracker/stats")
.done(function(data) {
console.log("Stats response received:", data);
if (data && data.response && data.response.result === "success" && data.response.data) {
console.log("Stats data is valid, loading activity...");
$("#embylivetv-active-timers").text(data.response.data.activeTimers || "0");
$("#embylivetv-series-timers").text(data.response.data.seriesTimers || "0");
' . (($compactView === 'false') ? '
$("#embylivetv-today-recordings").text(data.response.data.todaysRecordings || "0");
$("#embylivetv-total-recordings").text(data.response.data.totalRecordings || "0");
' : '') . '
// Load activity
homepageEmbyLiveTVTrackerActivity();
} else {
console.error("Stats response structure issue:", {
hasData: !!data,
hasResponse: !!(data && data.response),
result: data && data.response && data.response.result,
hasResponseData: !!(data && data.response && data.response.data)
});
console.error("Failed to load Emby LiveTV stats:", data.response ? data.response.message : "Unknown error");
$("#embylivetv-stats h3").text("?").attr("title", "Error loading data");
}
})
.fail(function(xhr, status, error) {
console.error("Error loading Emby LiveTV stats:", error);
$("#embylivetv-stats h3").text("!").attr("title", "Connection failed");
});
}
function homepageEmbyLiveTVTrackerActivity() {
console.log("Activity function called - making API request...");
return organizrAPI2("GET", "api/v2/homepage/embyLiveTVTracker/activity?days=' . ($daysShown ?: 7) . '\u0026limit=' . ($maxItems ?: 10) . '")
.done(function(data) {
console.log("Activity response received:", data);
console.log("Response structure check:", {
hasData: !!data,
hasResponse: !!(data && data.response),
result: data && data.response && data.response.result,
hasResponseData: !!(data && data.response && data.response.data),
hasActivities: !!(data && data.response && data.response.data && data.response.data.activities),
activitiesLength: data && data.response && data.response.data && data.response.data.activities ? data.response.data.activities.length : 0
});
if (data && data.response && data.response.result === "success" && data.response.data) {
var scheduledRecordings = data.response.data.scheduledRecordings || [];
var completedRecordings = data.response.data.completedRecordings || [];
console.log("Scheduled recordings:", scheduledRecordings.length);
console.log("Completed recordings:", completedRecordings.length);
// Apply limits to each category separately to ensure we show both types
var maxScheduled = Math.floor(' . $maxItems . ' * 0.7); // 70% for scheduled
var maxCompleted = ' . $maxItems . ' - maxScheduled; // 30% for completed
// If we have fewer scheduled than the 70% allocation, give more space to completed
if (scheduledRecordings.length < maxScheduled) {
maxCompleted = Math.min(completedRecordings.length, ' . $maxItems . ' - scheduledRecordings.length);
}
// If we have fewer completed than the 30% allocation, give more space to scheduled
if (completedRecordings.length < maxCompleted) {
maxScheduled = Math.min(scheduledRecordings.length, ' . $maxItems . ' - completedRecordings.length);
}
var scheduledActivities = scheduledRecordings.slice(0, maxScheduled);
var completedActivities = completedRecordings.slice(0, maxCompleted);
console.log("Split - Scheduled activities:", scheduledActivities.length);
console.log("Split - Completed activities:", completedActivities.length);
// Helper function to format scheduled activity rows
function formatScheduledRow(activity) {
var date = new Date(activity.date);
var formattedDate = date.toLocaleDateString() + " " + date.toLocaleTimeString([], {hour: "2-digit", minute:"2-digit"});
var status = activity.status || "Scheduled";
var statusClass = status.toLowerCase() === "completed" ? "status-success" :
status.toLowerCase() === "recording" ? "status-recording" : "status-scheduled";
return "<tr>" +
"<td><small>" + formattedDate + "</small></td>" +
"<td>" + (activity.seriesName || activity.name || "-") + "</td>" +
' . (($showSeriesInfo === 'true') ? '"<td><small>" + (activity.episodeTitle || activity.name || "-") + "</small></td>" +' : '') . '
"<td><small>" + (activity.channelName || "-") + "</small></td>" +
' . (($showUserInfo === 'true') ? '"<td><small>" + (activity.userName || "-") + "</small></td>" +' : '') . '
' . (($showDuration === 'true') ? '"<td><small>" + (activity.duration || "-") + "</small></td>" +' : '') . '
"<td><span class=\"" + statusClass + "\"><i class=\"fa fa-circle\"></i></span></td>" +
"</tr>";
}
// Helper function to format completed activity rows (no channel or user columns)
function formatCompletedRow(activity) {
var date = new Date(activity.date);
var formattedDate = date.toLocaleDateString() + " " + date.toLocaleTimeString([], {hour: "2-digit", minute:"2-digit"});
var status = activity.status || "Completed";
var statusClass = "status-success";
return "<tr>" +
"<td><small>" + formattedDate + "</small></td>" +
"<td>" + (activity.seriesName || activity.name || "-") + "</td>" +
' . (($showSeriesInfo === 'true') ? '"<td><small>" + (activity.seriesName || "-") + "</small></td>" +' : '') . '
' . (($showDuration === 'true') ? '"<td><small>" + (activity.duration || "-") + "</small></td>" +' : '') . '
"<td><span class=\"" + statusClass + "\"><i class=\"fa fa-circle\"></i></span></td>" +
"</tr>";
}
// Populate scheduled recordings table
var scheduledTable = $("#embylivetv-scheduled-table");
if (scheduledActivities.length === 0) {
scheduledTable.html("<tr><td colspan=\"' . (4 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0) + ($showUserInfo === 'true' ? 1 : 0)) . '\" class=\"text-center text-muted\">No scheduled recordings</td></tr>");
} else {
var scheduledRows = scheduledActivities.map(formatScheduledRow).join("");
scheduledTable.html(scheduledRows);
}
// Populate completed recordings table (only if enabled and table exists)
' . (($showCompleted === 'true') ? '
var completedTable = $("#embylivetv-completed-table");
if (completedActivities.length === 0) {
completedTable.html("<tr><td colspan=\"' . (3 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '\" class=\"text-center text-muted\">No completed recordings</td></tr>");
} else {
var completedRows = completedActivities.map(formatCompletedRow).join("");
completedTable.html(completedRows);
}
' : '') . '
} else {
$("#embylivetv-scheduled-table").html("<tr><td colspan=\"' . (4 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0) + ($showUserInfo === 'true' ? 1 : 0)) . '\" class=\"text-center text-muted\">No activity data available</td></tr>");
' . (($showCompleted === 'true') ? '$("#embylivetv-completed-table").html("<tr><td colspan=\"' . (3 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '\" class=\"text-center text-muted\">No activity data available</td></tr>");' : '') . '
}
})
.fail(function(xhr, status, error) {
console.error("Error loading Emby LiveTV activity:", error);
$("#embylivetv-scheduled-table").html("<tr><td colspan=\"' . (4 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showUserInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '\" class=\"text-center text-danger\">Error loading activity data</td></tr>");
' . (($showCompleted === 'true') ? '$("#embylivetv-completed-table").html("<tr><td colspan=\"' . (3 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '\" class=\"text-center text-danger\">Error loading activity data</td></tr>");' : '') . '
});
}
// Auto-refresh setup
var refreshInterval = ' . $refreshInterval . ';
if (refreshInterval > 0) {
embyLiveTVRefreshTimer = setInterval(function() {
refreshEmbyLiveTVData();
}, refreshInterval);
}
// Update time display every 30 seconds
setInterval(updateEmbyLiveTVLastRefreshTime, 30000);
// Initial load
$(document).ready(function() {
refreshEmbyLiveTVData();
});
// Cleanup timer when page unloads
$(window).on("beforeunload", function() {
if (embyLiveTVRefreshTimer) {
clearInterval(embyLiveTVRefreshTimer);
}
});
</script>
';
}
}
public function getHomepageEmbyLiveTVStats()
{
if (!$this->homepageItemPermissions($this->embyLiveTVTrackerHomepagePermissions('main'), true)) {
return false;
}
if (!$this->config['embyURL'] || !$this->config['embyToken']) {
$this->setAPIResponse('error', 'Emby URL or Token not configured', 500);
return false;
}
try {
$options = $this->requestOptions($this->config['embyURL'], null, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
$baseUrl = $this->qualifyURL($this->config['embyURL']);
$stats = [
'activeTimers' => 0,
'seriesTimers' => 0,
'todaysRecordings' => 0,
'totalRecordings' => 0,
'recentRecordings' => []
];
// Get active timers
$timersUrl = $baseUrl . '/emby/LiveTv/Timers?api_key=' . $this->config['embyToken'];
try {
$timersResponse = Requests::get($timersUrl, [], $options);
if ($timersResponse->success) {
$timers = json_decode($timersResponse->body, true);
$stats['activeTimers'] = count($timers['Items'] ?? []);
}
} catch (Exception $e) {
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get timers: ' . $e->getMessage());
}
// Get series timers
$seriesTimersUrl = $baseUrl . '/emby/LiveTv/SeriesTimers?api_key=' . $this->config['embyToken'];
try {
$seriesTimersResponse = Requests::get($seriesTimersUrl, [], $options);
if ($seriesTimersResponse->success) {
$seriesTimers = json_decode($seriesTimersResponse->body, true);
$stats['seriesTimers'] = count($seriesTimers['Items'] ?? []);
}
} catch (Exception $e) {
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get series timers: ' . $e->getMessage());
}
// Get recordings from the last 90 days
$recordingsUrl = $baseUrl . '/emby/LiveTv/Recordings?api_key=' . $this->config['embyToken'] . '&StartIndex=0&Limit=50&Fields=Overview,DateCreated&SortBy=DateCreated&SortOrder=Descending';
try {
$recordingsResponse = Requests::get($recordingsUrl, [], $options);
if ($recordingsResponse->success) {
$recordings = json_decode($recordingsResponse->body, true);
$allRecordings = $recordings['Items'] ?? [];
// Count today's recordings
$today = date('Y-m-d');
$todaysCount = 0;
$recentRecordings = [];
foreach ($allRecordings as $recording) {
if (isset($recording['DateCreated'])) {
$recordDate = date('Y-m-d', strtotime($recording['DateCreated']));
if ($recordDate === $today) {
$todaysCount++;
}
// Add to recent recordings list
$recentRecordings[] = [
'date' => $recordDate,
'program' => $recording['Name'] ?? 'Unknown',
'series' => $recording['SeriesName'] ?? '',
'channel' => $recording['ChannelName'] ?? 'Unknown Channel',
'status' => 'Completed'
];
}
}
$stats['todaysRecordings'] = $todaysCount;
$stats['totalRecordings'] = $recordings['TotalRecordCount'] ?? count($allRecordings);
$stats['recentRecordings'] = array_slice($recentRecordings, 0, 10); // Limit to 10 recent recordings
}
} catch (Exception $e) {
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get recordings: ' . $e->getMessage());
}
$this->setAPIResponse('success', 'LiveTV stats retrieved successfully', 200, $stats);
return true;
} catch (Exception $e) {
$this->setAPIResponse('error', 'Failed to retrieve LiveTV stats: ' . $e->getMessage(), 500);
return false;
}
}
public function getHomepageEmbyLiveTVActivity()
{
$debugEnabled = $this->config['homepageEmbyLiveTVTrackerDebug'] ?? false;
if (!$this->homepageItemPermissions($this->embyLiveTVTrackerHomepagePermissions('main'), true)) {
if ($debugEnabled) {
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Permission denied for user');
}
$this->setAPIResponse('error', 'Permission denied', 403);
return false;
}
if (!$this->config['embyURL'] || !$this->config['embyToken']) {
$this->setAPIResponse('error', 'Emby URL or Token not configured', 500);
return false;
}
try {
if ($debugEnabled) {
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Activity method called - starting execution');
}
$options = $this->requestOptions($this->config['embyURL'], null, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
$baseUrl = $this->qualifyURL($this->config['embyURL']);
if ($debugEnabled) {
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Base URL configured: ' . $baseUrl);
}
$scheduledRecordings = [];
$completedRecordings = [];
$maxItems = intval($this->config['homepageEmbyLiveTVTrackerMaxItems'] ?? 10);
$showCompleted = $this->config['homepageEmbyLiveTVTrackerShowCompleted'] ?? true;
$maxCompletedItems = intval($this->config['homepageEmbyLiveTVTrackerMaxCompletedItems'] ?? 5);
// Get user info if user info is enabled
$userMap = [];
$showUserInfo = $this->config['homepageEmbyLiveTVTrackerShowUserInfo'] ?? false;
if ($showUserInfo) {
$usersUrl = $baseUrl . '/emby/Users?api_key=' . $this->config['embyToken'];
try {
$usersResponse = Requests::get($usersUrl, [], $options);
if ($usersResponse->success) {
$users = json_decode($usersResponse->body, true);
foreach ($users as $user) {
$userMap[$user['Id']] = $user['Name'];
}
if ($debugEnabled) {
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Retrieved ' . count($userMap) . ' users for mapping');
}
}
} catch (Exception $e) {
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get users: ' . $e->getMessage());
}
}
// Get scheduled recordings (active timers)
$timersUrl = $baseUrl . '/emby/LiveTv/Timers?api_key=' . $this->config['embyToken'] . '&Fields=ChannelName,ChannelId,SeriesName,ProgramInfo,StartDate,EndDate,UserId';
if ($debugEnabled) {
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Fetching timers from URL: ' . $timersUrl);
}
try {
$timersResponse = Requests::get($timersUrl, [], $options);
if ($debugEnabled) {
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Timers API response status: ' . ($timersResponse->success ? 'success' : 'failed'));
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Timers API response code: ' . $timersResponse->status_code);
}
if ($timersResponse->success) {
$timers = json_decode($timersResponse->body, true);
$allTimers = $timers['Items'] ?? [];
if ($debugEnabled) {
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Retrieved ' . count($allTimers) . ' timers from Emby API');
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Timers API raw response length: ' . strlen($timersResponse->body));
$this->setLoggerChannel('EmbyLiveTVTracker')->info('First timer sample: ' . json_encode(array_slice($allTimers, 0, 1)));
}
// Sort timers by start date
usort($allTimers, function($a, $b) {
$aDate = $a['StartDate'] ?? '';
$bDate = $b['StartDate'] ?? '';
return strcmp($aDate, $bDate);
});
$timersToProcess = array_slice($allTimers, 0, intval($maxItems));
if ($debugEnabled) {
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Processing ' . count($timersToProcess) . ' timers (maxItems: ' . $maxItems . ')');
$this->setLoggerChannel('EmbyLiveTVTracker')->info('All timers count before slice: ' . count($allTimers));
$this->setLoggerChannel('EmbyLiveTVTracker')->info('MaxItems value: ' . $maxItems . ' (type: ' . gettype($maxItems) . ')');
}
foreach ($timersToProcess as $index => $timer) {
if ($debugEnabled) {
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Processing timer ' . ($index + 1) . ': ' . json_encode([
'Name' => $timer['Name'] ?? 'no name',
'StartDate' => $timer['StartDate'] ?? 'no start date',
'EndDate' => $timer['EndDate'] ?? 'no end date',
'ChannelName' => $timer['ChannelName'] ?? 'no channel name',
'Status' => $timer['Status'] ?? 'no status',
'UserId' => $timer['UserId'] ?? 'no user'
]));
}
// Calculate duration
$duration = '-';
if (isset($timer['StartDate']) && isset($timer['EndDate'])) {
$start = strtotime($timer['StartDate']);
$end = strtotime($timer['EndDate']);
if ($start && $end) {
$minutes = round(($end - $start) / 60);
$hours = floor($minutes / 60);
$remainingMinutes = $minutes % 60;
if ($hours > 0) {
$duration = sprintf('%dh %dm', $hours, $remainingMinutes);
} else {
$duration = sprintf('%dm', $minutes);
}
}
}
// Get channel name - timers should have this information
$channelName = $timer['ChannelName'] ?? null;
if (empty($channelName) && !empty($timer['ChannelId'])) {
$channelName = 'Channel ' . $timer['ChannelId'];
} elseif (empty($channelName)) {
$channelName = 'Unknown Channel';
}
// Get user name
$userName = null;
if ($showUserInfo && !empty($timer['UserId']) && isset($userMap[$timer['UserId']])) {
$userName = $userMap[$timer['UserId']];
}
// Determine status based on timing
$status = 'Scheduled';
$startTime = strtotime($timer['StartDate'] ?? '');
$endTime = strtotime($timer['EndDate'] ?? '');
$now = time();
if ($startTime && $endTime) {
if ($now >= $startTime && $now <= $endTime) {
$status = 'Recording';
} elseif ($now > $endTime) {
$status = 'Completed';
}
}
// Get series name and episode title - try multiple approaches
$seriesName = '';
$episodeTitle = '';
// Check for episode title first
if (!empty($timer['ProgramInfo']['EpisodeTitle'])) {
$episodeTitle = $timer['ProgramInfo']['EpisodeTitle'];
}
// Get series name
if (!empty($timer['SeriesName'])) {
$seriesName = $timer['SeriesName'];
} elseif (!empty($timer['ProgramInfo']['SeriesName'])) {
$seriesName = $timer['ProgramInfo']['SeriesName'];
} elseif (!empty($timer['ProgramInfo']['Name'])) {
$seriesName = $timer['ProgramInfo']['Name'];
} elseif (!empty($timer['Name'])) {
$seriesName = $timer['Name'];
}
// Use episode title if available, otherwise use program/series name
$displayName = $episodeTitle ? $episodeTitle : ($timer['Name'] ?? ($timer['ProgramInfo']['Name'] ?? 'Unknown Program'));
$activity = [
'date' => $timer['StartDate'] ?? '',
'name' => $displayName,
'seriesName' => $seriesName,
'episodeTitle' => $episodeTitle,
'channelName' => $channelName,
'userName' => $userName,
'duration' => $duration,
'status' => $status,
'type' => 'timer'
];
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Created activity for timer ' . ($index + 1) . ': ' . json_encode($activity));
// Debug timer date parsing
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Timer date debug - StartDate: "' . ($timer['StartDate'] ?? 'null') . '", parsed startTime: ' . ($startTime ? date('Y-m-d H:i:s', $startTime) : 'failed to parse') . ', EndDate: "' . ($timer['EndDate'] ?? 'null') . '", parsed endTime: ' . ($endTime ? date('Y-m-d H:i:s', $endTime) : 'failed to parse') . ', current time: ' . date('Y-m-d H:i:s', $now) . ', calculated status: ' . $status);
$scheduledRecordings[] = $activity;
}
}
} catch (Exception $e) {
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get timers for activity: ' . $e->getMessage());
}
// Get completed recordings if enabled
if ($showCompleted) {
$recordingsUrl = $baseUrl . '/emby/LiveTv/Recordings?api_key=' . $this->config['embyToken'] . '&StartIndex=0&Limit=' . $maxCompletedItems . '&Fields=DateCreated,SeriesName,RunTimeTicks&SortBy=DateCreated&SortOrder=Descending';
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Fetching completed recordings from URL: ' . $recordingsUrl);
try {
$recordingsResponse = Requests::get($recordingsUrl, [], $options);
if ($recordingsResponse->success) {
$recordings = json_decode($recordingsResponse->body, true);
$allRecordings = $recordings['Items'] ?? [];
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Retrieved ' . count($allRecordings) . ' completed recordings');
foreach ($allRecordings as $recording) {
if (isset($recording['DateCreated'])) {
// Format duration
$duration = '-';
if (isset($recording['RunTimeTicks']) && $recording['RunTimeTicks'] > 0) {
$minutes = floor($recording['RunTimeTicks'] / 600000000);
$hours = floor($minutes / 60);
$remainingMinutes = $minutes % 60;
if ($hours > 0) {
$duration = sprintf('%dh %dm', $hours, $remainingMinutes);
} else {
$duration = sprintf('%dm', $minutes);
}
}
$completedRecordings[] = [
'date' => $recording['DateCreated'],
'name' => $recording['Name'] ?? 'Unknown Program',
'seriesName' => $recording['SeriesName'] ?? '',
'channelName' => 'Unknown Channel', // Completed recordings don't have reliable channel info
'userName' => null, // No user info available for completed recordings
'duration' => $duration,
'status' => 'Completed',
'type' => 'recording'
];
}
}
}
} catch (Exception $e) {
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get completed recordings: ' . $e->getMessage());
}
}
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Final scheduled recordings count: ' . count($scheduledRecordings));
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Final completed recordings count: ' . count($completedRecordings));
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Sample scheduled: ' . json_encode(array_slice($scheduledRecordings, 0, 1)));
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Sample completed: ' . json_encode(array_slice($completedRecordings, 0, 1)));
$this->setAPIResponse('success', 'LiveTV activity retrieved successfully', 200, [
'scheduledRecordings' => $scheduledRecordings,
'completedRecordings' => $completedRecordings
]);
return true;
} catch (Exception $e) {
$this->setAPIResponse('error', 'Failed to retrieve LiveTV activity: ' . $e->getMessage(), 500);
return false;
}
}
}
?>

View File

@@ -0,0 +1,127 @@
<?php
trait HealthChecksHomepageItem
{
public function healthChecksSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'HealthChecks',
'enabled' => true,
'image' => 'plugins/images/tabs/healthchecks.png',
'category' => 'Monitor',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'docs' => $this->docs('features/homepage/healthchecks-homepage-item'),
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageHealthChecksEnabled'),
$this->settingsOption('auth', 'homepageHealthChecksAuth'),
],
'Connection' => [
$this->settingsOption('url', 'healthChecksURL'),
$this->settingsOption('multiple-token', 'healthChecksToken'),
$this->settingsOption('disable-cert-check', 'healthChecksDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'healthChecksUseCustomCertificate'),
],
'Misc Options' => [
$this->settingsOption('multiple', 'healthChecksTags', ['label' => 'Tags', 'help' => 'Pull only checks with this tag - Blank for all', 'placeholder' => 'Multiple tags using CSV - tag1,tag2']),
$this->settingsOption('refresh', 'homepageHealthChecksRefresh'),
$this->settingsOption('switch', 'homepageHealthChecksShowDesc', ['label' => 'Show Description']),
$this->settingsOption('switch', 'homepageHealthChecksShowTags', ['label' => 'Show Tags']),
],
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function healthChecksHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageHealthChecksEnabled'
],
'auth' => [
'homepageHealthChecksAuth'
],
'not_empty' => [
'healthChecksURL',
'healthChecksToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderhealthchecks()
{
if ($this->homepageItemPermissions($this->healthChecksHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Health Checks...</h2></div>
<script>
// Health Checks
homepageHealthChecks("' . $this->config['healthChecksTags'] . '","' . $this->config['homepageHealthChecksRefresh'] . '");
// End Health Checks
</script>
</div>
';
}
}
public function getHealthChecks($tags = null)
{
if (!$this->homepageItemPermissions($this->healthChecksHomepagePermissions('main'), true)) {
return false;
}
$api['content']['checks'] = array();
$tags = ($tags) ? $this->healthChecksTags($tags) : '';
$healthChecks = explode(',', $this->config['healthChecksToken']);
foreach ($healthChecks as $token) {
$url = $this->qualifyURL($this->config['healthChecksURL']) . '/' . $tags;
try {
$headers = array('X-Api-Key' => $token);
$options = $this->requestOptions($url, $this->config['homepageHealthChecksRefresh'], $this->config['healthChecksDisableCertCheck'], $this->config['healthChecksUseCustomCertificate']);
$response = Requests::get($url, $headers, $options);
if ($response->success) {
$healthResults = json_decode($response->body, true);
$api['content']['checks'] = array_merge($api['content']['checks'], $healthResults['checks']);
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('HealthChecks')->error($e);
};
}
usort($api['content']['checks'], function ($a, $b) {
return $a['status'] <=> $b['status'];
});
$api['options'] = [
'desc' => $this->config['homepageHealthChecksShowDesc'],
'tags' => $this->config['homepageHealthChecksShowTags'],
];
$api['content']['checks'] = isset($api['content']['checks']) ? $api['content']['checks'] : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
public function healthChecksTags($tags)
{
$return = '?tag=';
if (!$tags) {
return '';
} elseif ($tags == '*') {
return '';
} else {
if (strpos($tags, ',') !== false) {
$list = explode(',', $tags);
return $return . implode("&tag=", $list);
} else {
return $return . $tags;
}
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
trait HTMLHomepageItem
{
public function customHtmlNumber()
{
return 8;
}
public function customHtmlSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'CustomHTML',
'enabled' => strpos('personal,business', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/HTML5.png',
'category' => 'Custom',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => []
];
for ($i = 1; $i <= $this->customHtmlNumber(); $i++) {
$i = sprintf('%02d', $i);
$homepageSettings['settings']['Custom HTML ' . $i] = array(
$this->settingsOption('enable', 'homepageCustomHTML' . $i . 'Enabled'),
$this->settingsOption('auth', 'homepageCustomHTML' . $i . 'Auth'),
//$this->settingsOption('pre-code-editor', 'customHTML' . $i), // possibly can remove this as we consolidated the type into one
$this->settingsOption('code-editor', 'customHTML' . $i, ['label' => 'Custom HTML Code', 'mode' => 'html']),
);
}
return array_merge($homepageInformation, $homepageSettings);
}
public function htmlHomepagePermissions($key = null)
{
for ($i = 1; $i <= $this->customHtmlNumber(); $i++) {
$i = sprintf('%02d', $i);
$permissions[$i] = [
'enabled' => [
'homepageCustomHTML' . $i . 'Enabled'
],
'auth' => [
'homepageCustomHTML' . $i . 'Auth'
],
'not_empty' => [
'customHTML' . $i
]
];
}
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrdercustomhtml($key = '01')
{
if ($this->homepageItemPermissions($this->htmlHomepagePermissions($key))) {
return '
<div id="' . __FUNCTION__ . '">
' . $this->config['customHTML' . $key] . '
</div>
';
}
}
}

View File

@@ -0,0 +1,328 @@
<?php
trait ICalHomepageItem
{
public function calendarDaysCheck($entryStart, $entryEnd)
{
$success = false;
$entryStart = intval($entryStart);
$entryEnd = intval($entryEnd);
if ($entryStart >= 0 && $entryEnd <= 0) {
$success = true;
}
return $success;
}
public function calendarStandardizeTimezone($timezone)
{
switch ($timezone) {
case('CST'):
case('Central Time'):
case('Central Standard Time'):
$timezone = 'America/Chicago';
break;
case('CET'):
case('Central European Time'):
$timezone = 'Europe/Berlin';
break;
case('EST'):
case('Eastern Time'):
case('Eastern Standard Time'):
$timezone = 'America/New_York';
break;
case('MST'):
case('Mountain Time'):
case('Mountain Standard Time'):
$timezone = 'America/Denver';
break;
case('PST'):
case('Pacific Time'):
case('Pacific Standard Time'):
$timezone = 'America/Los_Angeles';
break;
case('AKST'):
case('Alaska Time'):
case('Alaska Standard Time'):
$timezone = 'America/Anchorage';
break;
case('HST'):
case('Hawaii Time'):
case('Hawaii Standard Time'):
$timezone = 'Pacific/Honolulu';
break;
case('China Time'):
case('China Standard Time'):
$timezone = 'Asia/Beijing';
break;
case('IST'):
case('India Time'):
case('India Standard Time'):
$timezone = 'Asia/New_Delhi';
break;
case('JST'):
case('Japan Time'):
case('Japan Standard Time'):
$timezone = 'Asia/Tokyo';
break;
case('WET'):
case('WEST'):
case('Western European Time'):
case('Western European Standard Time'):
case('Western European Summer Time'):
case('W. Europe Time'):
case('W. Europe Standard Time'):
case('W. Europe Summer Time'):
$timezone = 'Europe/Lisbon';
break;
}
return in_array($timezone, timezone_identifiers_list()) ? $timezone : 'UTC';
}
public function getCalendarExtraDates($start, $rule, $timezone)
{
$extraDates = [];
try {
if (stripos($rule, 'FREQ') !== false) {
$until = $this->getCalenderRepeatUntil($rule);
$start = new DateTime ($start);
$startDate = new DateTime ($this->currentTime);
$startDate->setTime($start->format('H'), $start->format('i'));
$startDate->modify('-' . $this->config['calendarStart'] . ' days');
$endDate = new DateTime ($this->currentTime);
$endDate->modify('+' . $this->config['calendarEnd'] . ' days');
$start = (stripos($rule, 'BYDAY') !== false || stripos($rule, 'BYMONTHDAY') !== false || stripos($rule, 'DAILY') !== false) ? $startDate : $start;
$until = $until ? new DateTime($until) : $endDate;
$dates = new \Recurr\Rule(trim($rule));
$dates->setStartDate($start)->setUntil($until);
$transformer = new \Recurr\Transformer\ArrayTransformer();
$transformerConfig = new \Recurr\Transformer\ArrayTransformerConfig();
$transformerConfig->enableLastDayOfMonthFix();
$transformer->setConfig($transformerConfig);
foreach (@$transformer->transform($dates) as $key => $date) {
if ($date->getStart() >= $startDate) {
$extraDates[$key]['start'] = $date->getStart();
$extraDates[$key]['end'] = $date->getEnd();
}
}
}
} catch (\Recurr\Exception\InvalidRRule|\Recurr\Exception\InvalidWeekday|Exception $e) {
return $extraDates;
}
return $extraDates;
}
public function getCalenderRepeat($value)
{
//FREQ=DAILY
//RRULE:FREQ=WEEKLY;BYDAY=TH
$first = explode('=', $value);
if (count($first) > 1) {
$second = explode(';', $first[1]);
} else {
return $value;
}
if ($second) {
return $second[0];
} else {
return $first[1];
}
}
public function getCalenderRepeatUntil($value)
{
$first = explode('UNTIL=', $value);
if (count($first) > 1) {
if (strpos($first[1], ';') !== false) {
$check = explode(';', $first[1]);
return $check[0];
} else {
return $first[1];
}
} else {
return false;
}
}
public function getCalenderRepeatCount($value)
{
$first = explode('COUNT=', $value);
if (count($first) > 1) {
return $first[1];
} else {
return false;
}
}
public function file_get_contents_curl($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_AUTOREFERER, true);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$data = curl_exec($ch);
curl_close($ch);
return $data;
}
public function getIcsEventsAsArray($file)
{
$icalString = $this->file_get_contents_curl($file);
$icsDates = array();
/* Explode the ICs Data to get datas as array according to string BEGIN: */
$icsData = explode('BEGIN:', $icalString);
/* Iterating the icsData value to make all the start end dates as sub array */
foreach ($icsData as $key => $value) {
$icsDatesMeta [$key] = explode("\n", $value);
}
/* Itearting the Ics Meta Value */
foreach ($icsDatesMeta as $key => $value) {
foreach ($value as $subKey => $subValue) {
/* to get ics events in proper order */
$icsDates = $this->getICSDates($key, $subKey, $subValue, $icsDates);
}
}
return $icsDates;
}
/* function is to avoid the elements which is not having the proper start, end and summary information */
public function getICSDates($key, $subKey, $subValue, $icsDates)
{
if ($key != 0 && $subKey == 0) {
$icsDates [$key] ['BEGIN'] = $subValue;
} else {
$subValueArr = explode(':', $subValue, 2);
if (isset ($subValueArr [1])) {
$icsDates [$key] [$subValueArr [0]] = $subValueArr [1];
}
}
return $icsDates;
}
public function retrieveCalenderByURL($url)
{
$events = [];
$icsEvents = $this->getIcsEventsAsArray($url);
if (isset($icsEvents) && !empty($icsEvents)) {
$timeZone = isset($icsEvents [1] ['X-WR-TIMEZONE']) ? trim($icsEvents[1]['X-WR-TIMEZONE']) : date_default_timezone_get();
$originalTimeZone = isset($icsEvents [1] ['X-WR-TIMEZONE']) ? str_replace('"', '', trim($icsEvents[1]['X-WR-TIMEZONE'])) : false;
unset($icsEvents [1]);
foreach ($icsEvents as $icsEvent) {
$startKeys = $this->array_filter_key($icsEvent, function ($key) {
return strpos($key, 'DTSTART') === 0;
});
$endKeys = $this->array_filter_key($icsEvent, function ($key) {
return strpos($key, 'DTEND') === 0;
});
if (!empty($startKeys) && !empty($endKeys) && isset($icsEvent['SUMMARY'])) {
/* Getting start date and time */
$dates = [];
$repeat = $icsEvent ['RRULE'] ?? false;
if (!$originalTimeZone) {
$tzKey = array_keys($startKeys);
if (strpos($tzKey[0], 'TZID=') !== false) {
$originalTimeZone = explode('TZID=', (string)$tzKey[0]);
$originalTimeZone = (count($originalTimeZone) >= 2) ? str_replace('"', '', $originalTimeZone[1]) : false;
$originalTimeZone = stripos($originalTimeZone, ';') !== false ? explode(';', $originalTimeZone)[0] : $originalTimeZone;
}
}
$start = reset($startKeys);
$end = reset($endKeys);
$oldestDay = new DateTime ($this->currentTime);
$oldestDay->modify('-' . $this->config['calendarStart'] . ' days');
$newestDay = new DateTime ($this->currentTime);
$newestDay->modify('+' . $this->config['calendarEnd'] . ' days');
if ($repeat) {
$dates = $this->getCalendarExtraDates($start, $icsEvent['RRULE'], $originalTimeZone);
} else {
$dates[] = [
'start' => new DateTime ($start),
'end' => new DateTime ($end)
];
if ($oldestDay > new DateTime ($end)) {
continue;
}
}
foreach ($dates as $eventDate) {
/* Converting to datetime and apply the timezone to get proper date time */
$startDt = $eventDate['start'];
/* Getting end date with time */
$endDt = $eventDate['end'];
$calendarStartDiff = date_diff($startDt, $newestDay);
$calendarEndDiff = date_diff($startDt, $oldestDay);
if ($originalTimeZone && $originalTimeZone !== 'UTC' && (strpos($start, 'Z') == false)) {
$originalTimeZone = $this->calendarStandardizeTimezone($originalTimeZone);
$dateTimeOriginalTZ = new DateTimeZone($originalTimeZone);
$dateTimeOriginal = new DateTime('now', $dateTimeOriginalTZ);
$dateTimeUTCTZ = new DateTimeZone(date_default_timezone_get());
$dateTimeUTC = new DateTime('now', $dateTimeUTCTZ);
$dateTimeOriginalOffset = $dateTimeOriginal->getOffset() / 3600;
$dateTimeUTCOffset = $dateTimeUTC->getOffset() / 3600;
$diff = $dateTimeUTCOffset - $dateTimeOriginalOffset;
if ((int)$diff >= 0) {
$startDt->modify('+ ' . $diff . ' hour');
$endDt->modify('+ ' . $diff . ' hour');
}
}
$startDt->setTimeZone(new DateTimezone ($timeZone));
$endDt->setTimeZone(new DateTimezone ($timeZone));
$startDate = $startDt->format(DateTime::ATOM);
$endDate = $endDt->format(DateTime::ATOM);
$dates = isset($icsEvent['RRULE']) ? $dates : null;
if (new DateTime() < $endDt) {
$extraClass = 'text-info';
} else {
$extraClass = 'text-success';
}
/* Getting the name of event */
$eventName = $icsEvent['SUMMARY'];
if (!$this->calendarDaysCheck($calendarStartDiff->format('%R') . $calendarStartDiff->days, $calendarEndDiff->format('%R') . $calendarEndDiff->days)) {
break;
}
if ($startDt->format('H') == 0 && $startDt->format('i') == 0) {
$startDate = $startDt->format('Y-m-d');
}
$events[] = array(
'title' => $eventName,
'imagetype' => 'calendar-o text-warning text-custom-calendar ' . $extraClass,
'imagetypeFilter' => 'ical',
'className' => 'bg-calendar calendar-item bg-custom-calendar',
'start' => (strlen(trim($start)) == 8) ? $eventDate['start']->format('Y-m-d') : $startDate,
'end' => (strlen(trim($end)) == 8) ? $eventDate['end']->format('Y-m-d') : $endDate,
'bgColor' => str_replace('text', 'bg', $extraClass),
);
}
}
}
}
return $events;
}
public function getICalendar()
{
if (!$this->config['homepageCalendarEnabled']) {
$this->setAPIResponse('error', 'iCal homepage item is not enabled', 409);
return false;
}
if (!$this->qualifyRequest($this->config['homepageCalendarAuth'])) {
$this->setAPIResponse('error', 'User not approved to view this homepage item', 401);
return false;
}
if (empty($this->config['calendariCal'])) {
$this->setAPIResponse('error', 'iCal URL is not defined', 422);
return false;
}
$iCalEvents = [];
$calendarURLList = explode(',', $this->config['calendariCal']);
foreach ($calendarURLList as $key => $value) {
$iCalEvents = array_merge($iCalEvents, $this->retrieveCalenderByURL($value));
}
$calendarSources = $iCalEvents;
$this->setAPIResponse('success', null, 200, $calendarSources);
return $calendarSources;
}
}

View File

@@ -0,0 +1,179 @@
<?php
trait JackettHomepageItem
{
public function jackettSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Jackett',
'enabled' => true,
'image' => 'plugins/images/tabs/jackett.png',
'category' => 'Utility',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageJackettEnabled'),
$this->settingsOption('auth', 'homepageJackettAuth'),
],
'Connection' => [
$this->settingsOption('url', 'jackettURL'),
$this->settingsOption('token', 'jackettToken'),
$this->settingsOption('disable-cert-check', 'jackettDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'jackettUseCustomCertificate'),
],
'Options' => [
$this->settingsOption('switch', 'homepageJackettBackholeDownload', ['label' => 'Prefer black hole download', 'help' => 'Prefer black hole download link instead of direct/magnet download']),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'jackett'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function jackettHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageJackettEnabled'
],
'auth' => [
'homepageJackettAuth'
],
'not_empty' => [
'jackettURL',
'jackettToken'
]
],
'test' => [
'auth' => [
'homepageJackettAuth'
],
'not_empty' => [
'jackettURL',
'jackettToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderJackett()
{
if ($this->homepageItemPermissions($this->jackettHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Jackett...</h2></div>
<script>
// Jackett
homepageJackett();
// End Jackett
</script>
</div>
';
}
}
public function testConnectionJackett()
{
if (!$this->homepageItemPermissions($this->jackettHomepagePermissions('test'), true)) {
return false;
}
$apiURL = $this->qualifyURL($this->config['jackettURL']);
$endpoint = $apiURL . '/api/v2.0/indexers/all/results?apikey=' . $this->config['jackettToken'] . '&Query=this-is-just-a-test-for-organizr';
try {
$headers = [];
$options = $this->requestOptions($apiURL, 120, $this->config['jackettDisableCertCheck'], $this->config['jackettUseCustomCertificate']);
$response = Requests::get($endpoint, $headers, $options);
if ($response->success) {
$apiData = json_decode($response->body, true);
$api['content'] = $apiData;
unset($apiData);
} else {
$this->setResponse(403, 'Error connecting to Jackett');
return false;
}
} catch (Requests_Exception $e) {
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = $api['content'] ?? false;
$this->setResponse(200, null, $api);
return $api;
}
public function searchJackettIndexers($query = null)
{
if (!$this->homepageItemPermissions($this->jackettHomepagePermissions('main'), true)) {
return false;
}
if (!$query) {
$this->setAPIResponse('error', 'Query was not supplied', 422);
return false;
}
$apiURL = $this->qualifyURL($this->config['jackettURL']);
$endpoint = $apiURL . '/api/v2.0/indexers/all/results?apikey=' . $this->config['jackettToken'] . '&Query=' . urlencode($query);
try {
$headers = [];
$options = $this->requestOptions($apiURL, 120, $this->config['jackettDisableCertCheck'], $this->config['jackettUseCustomCertificate']);
$response = Requests::get($endpoint, $headers, $options);
if ($response->success) {
$apiData = json_decode($response->body, true);
$api['content'] = $apiData;
unset($apiData);
}
} catch (Requests_Exception $e) {
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = isset($api['content']) ? $api['content'] : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
public function performJackettBackHoleDownload($url = null)
{
if (!$this->homepageItemPermissions($this->jackettHomepagePermissions('main'), true)) {
return false;
}
if (!$url) {
$this->setAPIResponse('error', 'URL was not supplied', 422);
return false;
}
$apiURL = $this->qualifyURL($this->config['jackettURL']);
$endpoint = $apiURL . $url;
error_log($endpoint);
try {
$headers = [];
$options = $this->requestOptions($apiURL, 120, $this->config['jackettDisableCertCheck'], $this->config['jackettUseCustomCertificate']);
$response = Requests::get($endpoint, $headers, $options);
if ($response->success) {
$apiData = json_decode($response->body, true);
$api['content'] = $apiData;
unset($apiData);
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Jackett')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = isset($api['content']) ? $api['content'] : false;
if ($api['content'] && $api['content']['result'] == 'success') {
$this->setAPIResponse('success', null, 200, $api);
} else if ($api['content']) {
$this->setAPIResponse('error', $api['content']['error'], 400, $api);
} else {
$this->setAPIResponse('error', 'Unknown error', 400, $api);
}
return $api;
}
}

View File

@@ -0,0 +1,181 @@
<?php
trait JDownloaderHomepageItem
{
public function jDownloaderSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'JDownloader',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/jdownloader.png',
'category' => 'Downloader',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'FYI' => [
$this->settingsOption('html', null, ['override' => 12, 'html' => '
<div class="row">
<div class="col-lg-12">
<div class="panel panel-info">
<div class="panel-heading">
<span lang="en">Notice</span>
</div>
<div class="panel-wrapper collapse in" aria-expanded="true">
<div class="panel-body">
<ul class="list-icons">
<li><i class="fa fa-chevron-right text-danger"></i> <a href="https://pypi.org/project/myjd-api/" target="_blank">Download [myjd-api] Module</a></li>
<li><i class="fa fa-chevron-right text-danger"></i> Add <b>/api/myjd</b> to the URL if you are using <a href="https://pypi.org/project/FeedCrawler/" target="_blank">FeedCrawler</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>']
),
],
'Enable' => [
$this->settingsOption('enable', 'homepageJdownloaderEnabled'),
$this->settingsOption('auth', 'homepageJdownloaderAuth'),
],
'Connection' => [
$this->settingsOption('url', 'jdownloaderURL'),
$this->settingsOption('username', 'jdownloaderUsername'),
$this->settingsOption('password', 'jdownloaderPassword'),
$this->settingsOption('blank'),
$this->settingsOption('disable-cert-check', 'jdownloaderDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'jdownloaderUseCustomCertificate'),
],
'Misc Options' => [
$this->settingsOption('refresh', 'jdownloaderRefresh'),
$this->settingsOption('combine', 'jdownloaderCombine'),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'jdownloader'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionJDownloader()
{
if (empty($this->config['jdownloaderURL'])) {
$this->setAPIResponse('error', 'JDownloader URL is not defined', 422);
return false;
}
if (!empty($this->config['jdownloaderUsername']) || !empty($this->config['jdownloaderPassword'])) {
$auth = array('auth' => array($this->config['jdownloaderUsername'], $this->decrypt($this->config['jdownloaderPassword'])));
} else {
$auth = [];
}
$url = $this->qualifyURL($this->config['jdownloaderURL']);
try {
$options = $this->requestOptions($url, $this->config['jdownloaderRefresh'], $this->config['jdownloaderDisableCertCheck'], $this->config['jdownloaderUseCustomCertificate'], $auth);
$response = Requests::get($url, [], $options);
if ($response->success) {
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
} else {
echo($response->body);
$this->setAPIResponse('success', 'JDownloader Error Occurred', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('JDownloader')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function jDownloaderHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageJdownloaderEnabled'
],
'auth' => [
'homepageJdownloaderAuth'
],
'not_empty' => [
'jdownloaderURL'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderjdownloader()
{
if ($this->homepageItemPermissions($this->jDownloaderHomepagePermissions('main'))) {
$loadingBox = ($this->config['jdownloaderCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
$builder = ($this->config['jdownloaderCombine']) ? 'buildDownloaderCombined(\'jdownloader\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("jdownloader"));';
return '
<div id="' . __FUNCTION__ . '">
' . $loadingBox . '
<script>
// homepageOrderjdownloader
' . $builder . '
homepageDownloader("jdownloader", "' . $this->config['jdownloaderRefresh'] . '");
// End homepageOrderjdownloader
</script>
</div>
';
}
}
public function getJdownloaderHomepageQueue()
{
if (!$this->homepageItemPermissions($this->jDownloaderHomepagePermissions('main'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['jdownloaderURL']);
if (!empty($this->config['jdownloaderUsername']) || !empty($this->config['jdownloaderPassword'])) {
$auth = array('auth' => array($this->config['jdownloaderUsername'], $this->decrypt($this->config['jdownloaderPassword'])));
} else {
$auth = [];
}
try {
$options = $this->requestOptions($url, $this->config['jdownloaderRefresh'], $this->config['jdownloaderDisableCertCheck'], $this->config['jdownloaderUseCustomCertificate'], $auth);
$response = Requests::get($url, [], $options);
if ($response->success) {
$temp = json_decode($response->body, true);
$packages = $temp['packages'];
if ($packages['downloader']) {
$api['content']['queueItems'] = $packages['downloader'];
} else {
$api['content']['queueItems'] = [];
}
if ($packages['linkgrabber_decrypted']) {
$api['content']['grabberItems'] = $packages['linkgrabber_decrypted'];
} else {
$api['content']['grabberItems'] = [];
}
if ($packages['linkgrabber_failed']) {
$api['content']['encryptedItems'] = $packages['linkgrabber_failed'];
} else {
$api['content']['encryptedItems'] = [];
}
if ($packages['linkgrabber_offline']) {
$api['content']['offlineItems'] = $packages['linkgrabber_offline'];
} else {
$api['content']['offlineItems'] = [];
}
$api['content']['$status'] = array($temp['downloader_state'], $temp['grabber_collecting'], $temp['update_ready']);
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('JDownloader')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = isset($api['content']) ? $api['content'] : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
}

View File

@@ -0,0 +1,519 @@
<?php
trait JellyfinHomepageItem
{
public function jellyfinSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Jellyfin',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/jellyfin.png',
'category' => 'Media Server',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageJellyfinEnabled'),
$this->settingsOption('auth', 'homepageJellyfinAuth'),
],
'Connection' => [
$this->settingsOption('url', 'jellyfinURL'),
$this->settingsOption('token', 'jellyfinToken'),
$this->settingsOption('disable-cert-check', 'jellyfinDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'jellyfinUseCustomCertificate'),
],
'Active Streams' => [
$this->settingsOption('enable', 'homepageJellyfinStreams'),
$this->settingsOption('auth', 'homepageJellyStreamsAuth'),
$this->settingsOption('switch', 'homepageShowStreamNames', ['label' => 'User Information']),
$this->settingsOption('auth', 'homepageShowStreamNamesAuth'),
$this->settingsOption('refresh', 'homepageStreamRefresh'),
],
'Recent Items' => [
$this->settingsOption('enable', 'homepageJellyfinRecent'),
$this->settingsOption('auth', 'homepageJellyfinRecentAuth'),
$this->settingsOption('limit', 'homepageRecentLimit'),
$this->settingsOption('refresh', 'homepageRecentRefresh'),
],
'Misc Options' => [
$this->settingsOption('input', 'homepageJellyfinLink', ['label' => 'Jellyfin Homepage Link URL', 'help' => 'Available variables: {id} {serverId}']),
$this->settingsOption('input', 'jellyfinTabName', ['label' => 'Jellyfin Tab Name', 'placeholder' => 'Only use if you have Jellyfin in a reverse proxy']),
$this->settingsOption('image-cache-quality', 'cacheImageSize'),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'jellyfin'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionJellyfin()
{
if (empty($this->config['jellyfinURL'])) {
$this->setAPIResponse('error', 'Jellyfin URL is not defined', 422);
return false;
}
if (empty($this->config['jellyfinToken'])) {
$this->setAPIResponse('error', 'Jellyfin Token is not defined', 422);
return false;
}
$url = $this->qualifyURL($this->config['jellyfinURL']);
$url = $url . "/Users?api_key=" . $this->config['jellyfinToken'];
$options = $this->requestOptions($url, null, $this->config['jellyfinDisableCertCheck'], $this->config['jellyfinUseCustomCertificate']);
try {
$response = Requests::get($url, [], $options);
if ($response->success) {
$json = json_decode($response->body);
if (is_array($json) || is_object($json)) {
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
} else {
$this->setAPIResponse('error', 'URL or token incorrect', 409);
return false;
}
} else {
$this->setAPIResponse('error', 'Jellyfin Connection Error', 500);
return true;
}
} catch (Requests_Exception $e) {
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function jellyfinHomepagePermissions($key = null)
{
$permissions = [
'streams' => [
'enabled' => [
'homepageJellyfinEnabled',
'homepageJellyfinStreams'
],
'auth' => [
'homepageJellyfinAuth',
'homepageJellyStreamsAuth'
],
'not_empty' => [
'jellyfinURL',
'jellyfinToken'
]
],
'recent' => [
'enabled' => [
'homepageJellyfinEnabled',
'homepageJellyfinRecent'
],
'auth' => [
'homepageJellyfinAuth',
'homepageJellyfinRecentAuth'
],
'not_empty' => [
'jellyfinURL',
'jellyfinToken'
]
],
'metadata' => [
'enabled' => [
'homepageJellyfinEnabled'
],
'auth' => [
'homepageJellyfinAuth'
],
'not_empty' => [
'jellyfinURL',
'jellyfinToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderjellyfinnowplaying()
{
if ($this->homepageItemPermissions($this->jellyfinHomepagePermissions('streams'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Now Playing...</h2></div>
<script>
// Jellyfin Stream
homepageStream("jellyfin", "' . $this->config['homepageStreamRefresh'] . '");
// End Jellyfin Stream
</script>
</div>
';
}
}
public function homepageOrderjellyfinrecent()
{
if ($this->homepageItemPermissions($this->jellyfinHomepagePermissions('recent'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Recent...</h2></div>
<script>
// Jellyfin Recent
homepageRecent("jellyfin", "' . $this->config['homepageRecentRefresh'] . '");
// End Jellyfin Recent
</script>
</div>
';
}
}
public function getJellyfinHomepageStreams()
{
if (!$this->homepageItemPermissions($this->jellyfinHomepagePermissions('streams'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['jellyfinURL']);
$url = $url . '/Sessions?api_key=' . $this->config['jellyfinToken'] . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines';
$options = $this->requestOptions($url, $this->config['homepageStreamRefresh'], $this->config['jellyfinDisableCertCheck'], $this->config['jellyfinUseCustomCertificate']);
try {
$response = Requests::get($url, [], $options);
if ($response->success) {
$items = array();
$jellyfin = json_decode($response->body, true);
foreach ($jellyfin as $child) {
if (isset($child['NowPlayingItem']) || isset($child['Name'])) {
$items[] = $this->resolveJellyfinItem($child);
}
}
$api['content'] = array_filter($items);
$this->setAPIResponse('success', null, 200, $api);
return $api;
} else {
$this->setAPIResponse('error', 'Jellyfin Error Occurred', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Jellyfin')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function getJellyfinHomepageRecent()
{
if (!$this->homepageItemPermissions($this->jellyfinHomepagePermissions('recent'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['jellyfinURL']);
$options = $this->requestOptions($url, $this->config['homepageRecentRefresh'], $this->config['jellyfinDisableCertCheck'], $this->config['jellyfinUseCustomCertificate']);
$username = false;
$showPlayed = false;
$userId = 0;
try {
if (isset($this->user['username'])) {
$username = strtolower($this->user['username']);
}
// Get A User
$userIds = $url . "/Users?api_key=" . $this->config['jellyfinToken'];
$response = Requests::get($userIds, [], $options);
if ($response->success) {
$jellyfin = json_decode($response->body, true);
foreach ($jellyfin as $value) { // Scan for admin user
if (isset($value['Policy']) && isset($value['Policy']['IsAdministrator']) && $value['Policy']['IsAdministrator']) {
$userId = $value['Id'];
}
if ($username && strtolower($value['Name']) == $username) {
$userId = $value['Id'];
$showPlayed = false;
break;
}
}
$url = $url . '/Users/' . $userId . '/Items/Latest?EnableImages=true&Limit=' . $this->config['homepageRecentLimit'] . '&api_key=' . $this->config['jellyfinToken'] . ($showPlayed ? '' : '&IsPlayed=false') . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines';
} else {
$this->setAPIResponse('error', 'Jellyfin Error Occurred', 500);
return false;
}
$response = Requests::get($url, [], $options);
if ($response->success) {
$items = array();
$jellyfin = json_decode($response->body, true);
foreach ($jellyfin as $child) {
if (isset($child['NowPlayingItem']) || isset($child['Name'])) {
$items[] = $this->resolveJellyfinItem($child);
}
}
$api['content'] = array_filter($items);
$this->setAPIResponse('success', null, 200, $api);
return $api;
} else {
$this->setAPIResponse('error', 'Jellyfin Error Occurred', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('JellyFin')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function getJellyfinHomepageMetadata($array)
{
if (!$this->homepageItemPermissions($this->jellyfinHomepagePermissions('metadata'), true)) {
return false;
}
$key = $array['key'] ?? null;
if (!$key) {
$this->setAPIResponse('error', 'Jellyfin Metadata key is not defined', 422);
return false;
}
$url = $this->qualifyURL($this->config['jellyfinURL']);
$options = $this->requestOptions($url, 60, $this->config['jellyfinDisableCertCheck'], $this->config['jellyfinUseCustomCertificate']);
$username = false;
$showPlayed = false;
$userId = 0;
try {
if (isset($this->user['username'])) {
$username = strtolower($this->user['username']);
}
// Get A User
$userIds = $url . "/Users?api_key=" . $this->config['jellyfinToken'];
$response = Requests::get($userIds, [], $options);
if ($response->success) {
$jellyfin = json_decode($response->body, true);
foreach ($jellyfin as $value) { // Scan for admin user
if (isset($value['Policy']) && isset($value['Policy']['IsAdministrator']) && $value['Policy']['IsAdministrator']) {
$userId = $value['Id'];
}
if ($username && strtolower($value['Name']) == $username) {
$userId = $value['Id'];
$showPlayed = false;
break;
}
}
$url = $url . '/Users/' . $userId . '/Items/' . $key . '?EnableImages=true&Limit=' . $this->config['homepageRecentLimit'] . '&api_key=' . $this->config['jellyfinToken'] . ($showPlayed ? '' : '&IsPlayed=false') . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines';
} else {
$this->setAPIResponse('error', 'Jellyfin Error Occurred', 500);
return false;
}
$response = Requests::get($url, [], $options);
if ($response->success) {
$items = array();
$jellyfin = json_decode($response->body, true);
if (isset($jellyfin['NowPlayingItem']) || isset($jellyfin['Name'])) {
$items[] = $this->resolveJellyfinItem($jellyfin);
}
$api['content'] = array_filter($items);
$this->setAPIResponse('success', null, 200, $api);
return $api;
} else {
$this->setAPIResponse('error', 'Jellyfin Error Occurred', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('JellyFin')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function resolveJellyfinItem($itemDetails)
{
$item = isset($itemDetails['NowPlayingItem']['Id']) ? $itemDetails['NowPlayingItem'] : $itemDetails;
// Static Height & Width
$height = $this->getCacheImageSize('h');
$width = $this->getCacheImageSize('w');
$nowPlayingHeight = $this->getCacheImageSize('nph');
$nowPlayingWidth = $this->getCacheImageSize('npw');
$actorHeight = 450;
$actorWidth = 300;
// Cache Directories
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$cacheDirectoryWeb = 'data/cache/';
// Types
switch (@$item['Type']) {
case 'Series':
$jellyfinItem['type'] = 'tv';
$jellyfinItem['title'] = $item['Name'];
$jellyfinItem['secondaryTitle'] = '';
$jellyfinItem['summary'] = '';
$jellyfinItem['ratingKey'] = $item['Id'];
$jellyfinItem['thumb'] = $item['Id'];
$jellyfinItem['key'] = $item['Id'] . "-list";
$jellyfinItem['nowPlayingThumb'] = $item['Id'];
$jellyfinItem['nowPlayingKey'] = $item['Id'] . "-np";
$jellyfinItem['metadataKey'] = $item['Id'];
$jellyfinItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? 'Thumb' : (isset($item['BackdropImageTags'][0]) ? 'Backdrop' : '');
break;
case 'Episode':
$jellyfinItem['type'] = 'tv';
$jellyfinItem['title'] = $item['SeriesName'];
$jellyfinItem['secondaryTitle'] = '';
$jellyfinItem['summary'] = '';
$jellyfinItem['ratingKey'] = $item['Id'];
$jellyfinItem['thumb'] = (isset($item['SeriesId']) ? $item['SeriesId'] : $item['Id']);
$jellyfinItem['key'] = (isset($item['SeriesId']) ? $item['SeriesId'] : $item['Id']) . "-list";
$jellyfinItem['nowPlayingThumb'] = isset($item['ParentThumbItemId']) ? $item['ParentThumbItemId'] : (isset($item['ParentBackdropItemId']) ? $item['ParentBackdropItemId'] : false);
$jellyfinItem['nowPlayingKey'] = isset($item['ParentThumbItemId']) ? $item['ParentThumbItemId'] . '-np' : (isset($item['ParentBackdropItemId']) ? $item['ParentBackdropItemId'] . '-np' : false);
$jellyfinItem['metadataKey'] = $item['Id'];
$jellyfinItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? 'Thumb' : (isset($item['ParentBackdropImageTags'][0]) ? 'Backdrop' : '');
$jellyfinItem['nowPlayingTitle'] = @$item['SeriesName'] . ' - ' . @$item['Name'];
$jellyfinItem['nowPlayingBottom'] = 'S' . @$item['ParentIndexNumber'] . ' · E' . @$item['IndexNumber'];
break;
case 'MusicAlbum':
case 'Audio':
$jellyfinItem['type'] = 'music';
$jellyfinItem['title'] = $item['Name'];
$jellyfinItem['secondaryTitle'] = '';
$jellyfinItem['summary'] = '';
$jellyfinItem['ratingKey'] = $item['Id'];
$jellyfinItem['thumb'] = $item['Id'];
$jellyfinItem['key'] = $item['Id'] . "-list";
$jellyfinItem['nowPlayingThumb'] = (isset($item['AlbumId']) ? $item['AlbumId'] : @$item['ParentBackdropItemId']);
$jellyfinItem['nowPlayingKey'] = $item['Id'] . "-np";
$jellyfinItem['metadataKey'] = isset($item['AlbumId']) ? $item['AlbumId'] : $item['Id'];
$jellyfinItem['nowPlayingImageType'] = (isset($item['ParentBackdropItemId']) ? "Primary" : "Backdrop");
$jellyfinItem['nowPlayingTitle'] = @$item['AlbumArtist'] . ' - ' . @$item['Name'];
$jellyfinItem['nowPlayingBottom'] = @$item['Album'];
break;
case 'Movie':
$jellyfinItem['type'] = 'movie';
$jellyfinItem['title'] = $item['Name'];
$jellyfinItem['secondaryTitle'] = '';
$jellyfinItem['summary'] = '';
$jellyfinItem['ratingKey'] = $item['Id'];
$jellyfinItem['thumb'] = $item['Id'];
$jellyfinItem['key'] = $item['Id'] . "-list";
$jellyfinItem['nowPlayingThumb'] = $item['Id'];
$jellyfinItem['nowPlayingKey'] = $item['Id'] . "-np";
$jellyfinItem['metadataKey'] = $item['Id'];
$jellyfinItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? "Thumb" : (isset($item['BackdropImageTags']) ? "Backdrop" : false);
$jellyfinItem['nowPlayingTitle'] = @$item['Name'];
$jellyfinItem['nowPlayingBottom'] = @$item['ProductionYear'];
break;
case 'Video':
$jellyfinItem['type'] = 'video';
$jellyfinItem['title'] = $item['Name'];
$jellyfinItem['secondaryTitle'] = '';
$jellyfinItem['summary'] = '';
$jellyfinItem['ratingKey'] = $item['Id'];
$jellyfinItem['thumb'] = $item['Id'];
$jellyfinItem['key'] = $item['Id'] . "-list";
$jellyfinItem['nowPlayingThumb'] = $item['Id'];
$jellyfinItem['nowPlayingKey'] = $item['Id'] . "-np";
$jellyfinItem['metadataKey'] = $item['Id'];
$jellyfinItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? "Thumb" : (isset($item['BackdropImageTags']) ? "Backdrop" : false);
$jellyfinItem['nowPlayingTitle'] = @$item['Name'];
$jellyfinItem['nowPlayingBottom'] = @$item['ProductionYear'];
break;
default:
return false;
}
$jellyfinItem['uid'] = $item['Id'];
$jellyfinItem['imageType'] = (isset($item['ImageTags']['Primary']) ? "Primary" : false);
$jellyfinItem['elapsed'] = isset($itemDetails['PlayState']['PositionTicks']) && $itemDetails['PlayState']['PositionTicks'] !== '0' ? (int)$itemDetails['PlayState']['PositionTicks'] : null;
$jellyfinItem['duration'] = isset($itemDetails['NowPlayingItem']['RunTimeTicks']) ? (int)$itemDetails['NowPlayingItem']['RunTimeTicks'] : (int)(isset($item['RunTimeTicks']) ? $item['RunTimeTicks'] : '');
$jellyfinItem['watched'] = ($jellyfinItem['elapsed'] && $jellyfinItem['duration'] ? floor(($jellyfinItem['elapsed'] / $jellyfinItem['duration']) * 100) : 0);
$jellyfinItem['transcoded'] = isset($itemDetails['TranscodingInfo']['CompletionPercentage']) ? floor((int)$itemDetails['TranscodingInfo']['CompletionPercentage']) : 100;
$jellyfinItem['stream'] = @$itemDetails['PlayState']['PlayMethod'];
$jellyfinItem['id'] = $item['ServerId'];
$jellyfinItem['session'] = @$itemDetails['DeviceId'];
$jellyfinItem['bandwidth'] = isset($itemDetails['TranscodingInfo']['Bitrate']) ? $itemDetails['TranscodingInfo']['Bitrate'] / 1000 : '';
$jellyfinItem['bandwidthType'] = 'wan';
$jellyfinItem['sessionType'] = (@$itemDetails['PlayState']['PlayMethod'] == 'Transcode') ? 'Transcoding' : 'Direct Playing';
$jellyfinItem['state'] = ((@(string)$itemDetails['PlayState']['IsPaused'] == '1') ? "pause" : "play");
$jellyfinItem['user'] = ($this->config['homepageShowStreamNames'] && $this->qualifyRequest($this->config['homepageShowStreamNamesAuth'])) ? @(string)$itemDetails['UserName'] : "";
$jellyfinItem['userThumb'] = '';
$jellyfinItem['userAddress'] = (isset($itemDetails['RemoteEndPoint']) ? $itemDetails['RemoteEndPoint'] : "x.x.x.x");
$jellfinVariablesForLink = [
'{id}' => $jellyfinItem['uid'],
'{serverId}' => $jellyfinItem['id']
];
$jellyfinItem['address'] = $this->userDefinedIdReplacementLink($this->config['homepageJellyfinLink'], $jellfinVariablesForLink);
$jellyfinItem['nowPlayingOriginalImage'] = 'api/v2/homepage/image?source=jellyfin&type=' . $jellyfinItem['nowPlayingImageType'] . '&img=' . $jellyfinItem['nowPlayingThumb'] . '&height=' . $nowPlayingHeight . '&width=' . $nowPlayingWidth . '&key=' . $jellyfinItem['nowPlayingKey'] . '$' . $this->randString();
$jellyfinItem['originalImage'] = 'api/v2/homepage/image?source=jellyfin&type=' . $jellyfinItem['imageType'] . '&img=' . $jellyfinItem['thumb'] . '&height=' . $height . '&width=' . $width . '&key=' . $jellyfinItem['key'] . '$' . $this->randString();
$jellyfinItem['openTab'] = (bool)$this->config['jellyfinTabName'];
$jellyfinItem['tabName'] = $this->config['jellyfinTabName'] ?: '';
// Stream info
$jellyfinItem['userStream'] = array(
'platform' => @(string)$itemDetails['Client'],
'product' => @(string)$itemDetails['Client'],
'device' => @(string)$itemDetails['DeviceName'],
'stream' => @$itemDetails['PlayState']['PlayMethod'],
'videoResolution' => isset($itemDetails['NowPlayingItem']['MediaStreams'][0]['Width']) ? $itemDetails['NowPlayingItem']['MediaStreams'][0]['Width'] : '',
'throttled' => false,
'sourceVideoCodec' => isset($itemDetails['NowPlayingItem']['MediaStreams'][0]) ? $itemDetails['NowPlayingItem']['MediaStreams'][0]['Codec'] : '',
'videoCodec' => @$itemDetails['TranscodingInfo']['VideoCodec'],
'audioCodec' => @$itemDetails['TranscodingInfo']['AudioCodec'],
'sourceAudioCodec' => isset($itemDetails['NowPlayingItem']['MediaStreams'][1]) ? $itemDetails['NowPlayingItem']['MediaStreams'][1]['Codec'] : (isset($itemDetails['NowPlayingItem']['MediaStreams'][0]) ? $itemDetails['NowPlayingItem']['MediaStreams'][0]['Codec'] : ''),
'videoDecision' => $this->streamType(@$itemDetails['PlayState']['PlayMethod']),
'audioDecision' => $this->streamType(@$itemDetails['PlayState']['PlayMethod']),
'container' => isset($itemDetails['NowPlayingItem']['Container']) ? $itemDetails['NowPlayingItem']['Container'] : '',
'audioChannels' => @$itemDetails['TranscodingInfo']['AudioChannels']
);
// Genre catch all
if (isset($item['Genres'])) {
$genres = array();
foreach ($item['Genres'] as $genre) {
$genres[] = $genre;
}
}
// Actor catch all
if (isset($item['People'])) {
$actors = array();
foreach ($item['People'] as $key => $value) {
if (@$value['PrimaryImageTag'] && @$value['Role']) {
if (file_exists($cacheDirectory . (string)$value['Id'] . '-cast.jpg')) {
$actorImage = $cacheDirectoryWeb . (string)$value['Id'] . '-cast.jpg';
}
if (file_exists($cacheDirectory . (string)$value['Id'] . '-cast.jpg') && (time() - 604800) > filemtime($cacheDirectory . (string)$value['Id'] . '-cast.jpg') || !file_exists($cacheDirectory . (string)$value['Id'] . '-cast.jpg')) {
$actorImage = 'api/v2/homepage/image?source=jellyfin&type=Primary&img=' . (string)$value['Id'] . '&height=' . $actorHeight . '&width=' . $actorWidth . '&key=' . (string)$value['Id'] . '-cast';
}
$actors[] = array(
'name' => (string)$value['Name'],
'role' => (string)$value['Role'],
'thumb' => $actorImage
);
}
}
}
// Metadata information
$jellyfinItem['metadata'] = array(
'guid' => $item['Id'],
'summary' => @(string)$item['Overview'],
'rating' => @(string)$item['CommunityRating'],
'duration' => @(string)$item['RunTimeTicks'],
'originallyAvailableAt' => @(string)$item['PremiereDate'],
'year' => (string)isset($item['ProductionYear']) ? $item['ProductionYear'] : '',
//'studio' => (string)$item['studio'],
'tagline' => @(string)$item['Taglines'][0],
'genres' => (isset($item['Genres'])) ? $genres : '',
'actors' => (isset($item['People'])) ? $actors : ''
);
if (file_exists($cacheDirectory . $jellyfinItem['nowPlayingKey'] . '.jpg')) {
$jellyfinItem['nowPlayingImageURL'] = $cacheDirectoryWeb . $jellyfinItem['nowPlayingKey'] . '.jpg';
}
if (file_exists($cacheDirectory . $jellyfinItem['key'] . '.jpg')) {
$jellyfinItem['imageURL'] = $cacheDirectoryWeb . $jellyfinItem['key'] . '.jpg';
}
if (file_exists($cacheDirectory . $jellyfinItem['nowPlayingKey'] . '.jpg') && (time() - 604800) > filemtime($cacheDirectory . $jellyfinItem['nowPlayingKey'] . '.jpg') || !file_exists($cacheDirectory . $jellyfinItem['nowPlayingKey'] . '.jpg')) {
$jellyfinItem['nowPlayingImageURL'] = 'api/v2/homepage/image?source=jellyfin&type=' . $jellyfinItem['nowPlayingImageType'] . '&img=' . $jellyfinItem['nowPlayingThumb'] . '&height=' . $nowPlayingHeight . '&width=' . $nowPlayingWidth . '&key=' . $jellyfinItem['nowPlayingKey'] . '';
}
if (file_exists($cacheDirectory . $jellyfinItem['key'] . '.jpg') && (time() - 604800) > filemtime($cacheDirectory . $jellyfinItem['key'] . '.jpg') || !file_exists($cacheDirectory . $jellyfinItem['key'] . '.jpg')) {
$jellyfinItem['imageURL'] = 'api/v2/homepage/image?source=jellyfin&type=' . $jellyfinItem['imageType'] . '&img=' . $jellyfinItem['thumb'] . '&height=' . $height . '&width=' . $width . '&key=' . $jellyfinItem['key'] . '';
}
if (!$jellyfinItem['nowPlayingThumb']) {
$jellyfinItem['nowPlayingOriginalImage'] = $jellyfinItem['nowPlayingImageURL'] = "plugins/images/homepage/no-np.png";
$jellyfinItem['nowPlayingKey'] = "no-np";
}
if (!$jellyfinItem['thumb']) {
$jellyfinItem['originalImage'] = $jellyfinItem['imageURL'] = "plugins/images/homepage/no-list.png";
$jellyfinItem['key'] = "no-list";
}
if (isset($useImage)) {
$jellyfinItem['useImage'] = $useImage;
}
return $jellyfinItem;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,293 @@
<?php
trait LidarrHomepageItem
{
public function lidarrSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Lidarr',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/lidarr.png',
'category' => 'PMR',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageLidarrEnabled'),
$this->settingsOption('auth', 'homepageLidarrAuth'),
],
'Connection' => [
$this->settingsOption('multiple-url', 'lidarrURL'),
$this->settingsOption('multiple-token', 'lidarrToken'),
$this->settingsOption('disable-cert-check', 'lidarrDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'lidarrUseCustomCertificate'),
],
'API SOCKS' => [
$this->settingsOption('socks', 'lidarr'),
$this->settingsOption('blank'),
$this->settingsOption('enable', 'lidarrSocksEnabled'),
$this->settingsOption('auth', 'lidarrSocksAuth'),
],
'Calendar' => [
$this->settingsOption('calendar-start', 'calendarStart'),
$this->settingsOption('calendar-end', 'calendarEnd'),
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
$this->settingsOption('calendar-default-view', 'calendarDefault'),
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
$this->settingsOption('calendar-locale', 'calendarLocale'),
$this->settingsOption('calendar-limit', 'calendarLimit'),
$this->settingsOption('refresh', 'calendarRefresh'),
$this->settingsOption('blank', '', ['type' => 'html', 'html' => '<hr />']),
$this->settingsOption('blank', '', ['type' => 'html', 'html' => '<hr />']),
$this->settingsOption('enable', 'lidarrIcon', ['label' => 'Show Lidarr Icon']),
$this->settingsOption('calendar-link-url', 'lidarrCalendarLink'),
$this->settingsOption('blank'),
$this->settingsOption('calendar-frame-target', 'lidarrFrameTarget')
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'lidarr'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionLidarr()
{
if (empty($this->config['lidarrURL'])) {
$this->setAPIResponse('error', 'Lidarr URL is not defined', 422);
return false;
}
if (empty($this->config['lidarrToken'])) {
$this->setAPIResponse('error', 'Lidarr Token is not defined', 422);
return false;
}
$failed = false;
$errors = '';
$list = $this->csvHomepageUrlToken($this->config['lidarrURL'], $this->config['lidarrToken']);
foreach ($list as $key => $value) {
try {
$options = $this->requestOptions($value['url'], null, $this->config['lidarrDisableCertCheck'], $this->config['lidarrUseCustomCertificate']);
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'lidarr', null . null, $options);
$results = $downloader->getRootFolder();
$downloadList = json_decode($results, true);
if (is_array($downloadList) || is_object($downloadList)) {
$queue = (array_key_exists('error', $downloadList)) ? $downloadList['error']['msg'] : $downloadList;
if (!is_array($queue)) {
$ip = $value['url'];
$errors .= $ip . ': ' . $queue;
$failed = true;
}
} else {
$ip = $value['url'];
$errors .= $ip . ': Response was not JSON';
$failed = true;
}
} catch (Exception $e) {
$failed = true;
$ip = $value['url'];
$errors .= $ip . ': ' . $e->getMessage();
$this->setLoggerChannel('Lidarr')->error($e);
}
}
if ($failed) {
$this->setAPIResponse('error', $errors, 500);
return false;
} else {
$this->setAPIResponse('success', null, 200);
return true;
}
}
public function lidarrHomepagePermissions($key = null)
{
$permissions = [
'calendar' => [
'enabled' => [
'homepageLidarrEnabled'
],
'auth' => [
'homepageLidarrAuth'
],
'not_empty' => [
'lidarrURL',
'lidarrToken'
]
],
'queue' => [
'enabled' => [
'homepageLidarrEnabled',
'homepageLidarrQueueEnabled'
],
'auth' => [
'homepageLidarrAuth',
'homepageLidarrQueueAuth'
],
'not_empty' => [
'lidarrURL',
'lidarrToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function getLidarrQueue()
{
if (!$this->homepageItemPermissions($this->lidarrHomepagePermissions('queue'), true)) {
return false;
}
$queueItems = array();
$list = $this->csvHomepageUrlToken($this->config['lidarrURL'], $this->config['lidarrToken']);
foreach ($list as $key => $value) {
try {
$options = $this->requestOptions($value['url'], null, $this->config['lidarrDisableCertCheck'], $this->config['lidarrUseCustomCertificate']);
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'lidarr', null, null, $options);
$results = $downloader->getQueue();
$downloadList = json_decode($results, true);
if (is_array($downloadList) || is_object($downloadList)) {
$queue = (array_key_exists('error', $downloadList)) ? '' : $downloadList;
} else {
$queue = '';
}
if (!empty($queue)) {
$queueItems = array_merge($queueItems, $queue);
}
} catch (Exception $e) {
$this->setLoggerChannel('Lidarr')->error($e);
}
}
$api['content']['queueItems'] = $queueItems;
$api['content']['historyItems'] = false;
$api['content'] = isset($api['content']) ? $api['content'] : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;;
}
public function getLidarrCalendar($startDate = null, $endDate = null)
{
$startDate = ($startDate) ?? $_GET['start'] ?? date('Y-m-d', strtotime('-' . $this->config['calendarStart'] . ' days'));
$endDate = ($endDate) ?? $_GET['end'] ?? date('Y-m-d', strtotime('+' . $this->config['calendarEnd'] . ' days'));
if (!$this->homepageItemPermissions($this->lidarrHomepagePermissions('calendar'), true)) {
return false;
}
if ($this->demo) {
return $this->demoData('lidarr/calendar.json');
}
$calendarItems = array();
$list = $this->csvHomepageUrlToken($this->config['lidarrURL'], $this->config['lidarrToken']);
foreach ($list as $key => $value) {
try {
$options = $this->requestOptions($value['url'], null, $this->config['lidarrDisableCertCheck'], $this->config['lidarrUseCustomCertificate']);
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'lidarr', null, null, $options);
$results = $downloader->getCalendar($startDate, $endDate);
$result = json_decode($results, true);
if (is_array($result) || is_object($result)) {
$calendar = (array_key_exists('error', $result)) ? '' : $this->formatLidarrCalendar($results, $key);
} else {
$calendar = '';
}
} catch (Exception $e) {
$this->setLoggerChannel('Lidarr')->error($e);
}
if (!empty($calendar)) {
$calendarItems = array_merge($calendarItems, $calendar);
}
}
$this->setAPIResponse('success', null, 200, $calendarItems);
return $calendarItems;
}
public function formatLidarrCalendar($array, $number)
{
$array = json_decode($array, true);
$gotCalendar = array();
$i = 0;
foreach ($array as $child) {
$i++;
$albumName = $child['title'];
$artistName = $child['artist']['artistName'];
$albumID = '';
$releaseDate = $child['releaseDate'];
$releaseDate = strtotime($releaseDate);
$releaseDate = date("Y-m-d H:i:s", $releaseDate);
if (new DateTime() < new DateTime($releaseDate)) {
$unaired = true;
}
if (isset($child['statistics']['percentOfTracks'])) {
if ($child['statistics']['percentOfTracks'] == '100.0') {
$downloaded = '1';
} else {
$downloaded = '0';
}
} else {
$downloaded = '0';
}
if ($downloaded == "0" && isset($unaired)) {
$downloaded = "text-info";
} elseif ($downloaded == "1") {
$downloaded = "text-success";
} else {
$downloaded = "text-danger";
}
$fanart = "/plugins/images/homepage/no-np.png";
foreach ($child['artist']['images'] as $image) {
if ($image['coverType'] == "fanart") {
$fanart = str_replace('http://', 'https://', $image['url']);
}
}
$href = $this->config['lidarrCalendarLink'] ?? '';
if (empty($href) && !empty($this->config['lidarrURL'])) {
$href_arr = explode(',', $this->config['lidarrURL']);
$href = reset($href_arr);
}
if (!empty($href)) {
$href = $href . '/artist/' . $child['artist']['foreignArtistId'];
$href = str_replace("//artist/", "/artist/", $href);
}
$details = array(
"seasonCount" => '',
"status" => '',
"topTitle" => $albumName,
"bottomTitle" => $artistName,
"overview" => isset($child['artist']['overview']) ? $child['artist']['overview'] : '',
"runtime" => '',
"image" => $fanart,
"ratings" => $child['artist']['ratings']['value'],
"videoQuality" => "unknown",
"audioChannels" => "unknown",
"audioCodec" => "unknown",
"videoCodec" => "unknown",
"size" => "unknown",
"genres" => $child['genres'],
"href" => strtolower($href),
"icon" => "/plugins/images/tabs/lidarr.png",
"frame" => $this->config['lidarrFrameTarget'],
"showLink" => $this->config['lidarrIcon']
);
array_push($gotCalendar, array(
"id" => "Lidarr-" . $number . "-" . $i,
"title" => $artistName,
"start" => $child['releaseDate'],
"className" => "inline-popups bg-calendar calendar-item musicID--",
"imagetype" => "music " . $downloaded,
"imagetypeFilter" => "music",
"downloadFilter" => $downloaded,
"bgColor" => str_replace('text', 'bg', $downloaded),
"details" => $details,
"data" => $child
));
}
if ($i != 0) {
return $gotCalendar;
}
return false;
}
}

View File

@@ -0,0 +1,29 @@
<?php
trait MiscHomepageItem
{
public function miscSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Misc',
'enabled' => true,
'image' => 'plugins/images/organizr/logo-no-border.png',
'category' => 'Custom',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'YouTube' => [
$this->settingsOption('token', 'youtubeAPI', ['label' => 'Youtube API Key', 'help' => 'Please make sure to input this API key as the organizr one gets limited']),
$this->settingsOption('html', null, ['override' => 6, 'label' => 'Instructions', 'html' => '<a href="https://www.slickremix.com/docs/get-api-key-for-youtube/" target="_blank">Click here for instructions</a>']),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
}

View File

@@ -0,0 +1,171 @@
<?php
trait MonitorrHomepageItem
{
public function monitorrSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Monitorr',
'enabled' => true,
'image' => 'plugins/images/tabs/monitorr.png',
'category' => 'Monitor',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageMonitorrEnabled'),
$this->settingsOption('auth', 'homepageMonitorrAuth'),
],
'Connection' => [
$this->settingsOption('url', 'monitorrURL', ['help' => 'URL for Monitorr. Please use the reverse proxy URL i.e. https://domain.com/monitorr/.', 'placeholder' => 'http://domain.com/monitorr/']),
$this->settingsOption('blank'),
$this->settingsOption('disable-cert-check', 'monitorrDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'monitorrUseCustomCertificate'),
],
'Options' => [
$this->settingsOption('refresh', 'homepageMonitorrRefresh'),
$this->settingsOption('switch', 'monitorrCompact', ['label' => 'Compact view', 'help' => 'Toggles the compact view of this homepage module']),
$this->settingsOption('title', 'monitorrHeader'),
$this->settingsOption('toggle-title', 'monitorrHeaderToggle'),
],
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function monitorrHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageMonitorrEnabled'
],
'auth' => [
'homepageMonitorrAuth'
],
'not_empty' => [
'monitorrURL'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderMonitorr()
{
if ($this->homepageItemPermissions($this->monitorrHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Monitorr...</h2></div>
<script>
// Monitorr
homepageMonitorr("' . $this->config['homepageMonitorrRefresh'] . '");
// End Monitorr
</script>
</div>
';
}
}
public function getMonitorrHomepageData()
{
if (!$this->homepageItemPermissions($this->monitorrHomepagePermissions('main'), true)) {
return false;
}
$api = [];
$url = $this->qualifyURL($this->config['monitorrURL']);
$dataUrl = $url . '/assets/php/loop.php';
try {
$options = $this->requestOptions($url, $this->config['homepageMonitorrRefresh'], $this->config['monitorrDisableCertCheck'], $this->config['monitorrUseCustomCertificate']);
$response = Requests::get($dataUrl, ['Token' => $this->config['organizrAPI']], $options);
if ($response->success) {
$html = html_entity_decode($response->body);
// This section grabs the names of all services by regex
$services = [];
$servicesMatch = [];
$servicePattern = '/<div id="servicetitle(?:offline|nolink)?".*><div>(.*)<\/div><\/div><div class="(?:btnonline|btnoffline|btnunknown)".*>(Online|Offline|Unresponsive)<\/div>(:?<\/a>)?<\/div><\/div>/';
preg_match_all($servicePattern, $html, $servicesMatch);
$services = array_filter($servicesMatch[1]);
$status = array_filter($servicesMatch[2]);
$statuses = [];
foreach ($services as $key => $service) {
$match = $status[$key];
$statuses[$service] = $match;
if ($match == 'Online') {
$statuses[$service] = [
'status' => true
];
} else if ($match == 'Offline') {
$statuses[$service] = [
'status' => false
];
} else if ($match == 'Unresponsive') {
$statuses[$service] = [
'status' => 'unresponsive'
];
}
$statuses[$service]['sort'] = $key;
$imageMatch = [];
$imgPattern = '/assets\/img\/\.\.(.*)" class="serviceimg" alt=.*><\/div><\/div><div id="servicetitle"><div>' . $service . '|assets\/img\/\.\.(.*)" class="serviceimg imgoffline" alt=.*><\/div><\/div><div id="servicetitleoffline".*><div>' . $service . '|assets\/img\/\.\.(.*)" class="serviceimg" alt=.*><\/div><\/div><div id="servicetitlenolink".*><div>' . $service . '/';
preg_match($imgPattern, $html, $imageMatch);
unset($imageMatch[0]);
$imageMatch = array_values($imageMatch);
// array_push($api['imagematches'][$service], $imageMatch);
foreach ($imageMatch as $match) {
if ($match !== '') {
$image = $match;
}
}
$ext = explode('.', $image);
$ext = $ext[key(array_slice($ext, -1, 1, true))];
$imageUrl = $url . '/assets' . $image;
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$img = Requests::get($imageUrl, ['Token' => $this->config['organizrAPI']], $options);
if ($img->success) {
$base64 = 'data:image/' . $ext . ';base64,' . base64_encode($img->body);
$statuses[$service]['image'] = $base64;
} else {
$statuses[$service]['image'] = 'plugins/images/homepage/no-list.png';
}
$linkMatch = [];
$linkPattern = '/<a class="servicetile" href="(.*)" target="_blank" style="display: block"><div id="serviceimg"><div><img id="' . strtolower($service) . '-service-img/';
preg_match($linkPattern, $html, $linkMatch);
$linkMatch = array_values($linkMatch);
unset($linkMatch[0]);
foreach ($linkMatch as $link) {
if ($link !== '') {
$statuses[$service]['link'] = $link;
}
}
}
foreach ($statuses as $status) {
foreach ($status as $key => $value) {
if (!isset($sortArray[$key])) {
$sortArray[$key] = array();
}
$sortArray[$key][] = $value;
}
}
array_multisort($sortArray['status'], SORT_ASC, $sortArray['sort'], SORT_ASC, $statuses);
$api['services'] = $statuses;
$api['options'] = [
'title' => $this->config['monitorrHeader'],
'titleToggle' => $this->config['monitorrHeaderToggle'],
'compact' => $this->config['monitorrCompact'],
];
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Monitorr')->error($e);
$this->setAPIResponse('error', $e->getMessage(), 401);
return false;
};
$api = isset($api) ? $api : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
}

View File

@@ -0,0 +1,457 @@
<?php
trait NetDataHomepageItem
{
public function getNetdataHomepageData()
{
if (!$this->homepageItemPermissions($this->netdataHomepagePermissions('main'), true)) {
return false;
}
$api = [];
$api['data'] = [];
$api['url'] = $this->config['netdataURL'];
$url = $this->qualifyURL($this->config['netdataURL']);
for ($i = 1; $i < 8; $i++) {
if ($this->config['netdata' . ($i) . 'Enabled']) {
switch ($this->config['netdata' . $i . 'Data']) {
case 'disk-read':
$data = $this->disk('in', $url);
break;
case 'disk-write':
$data = $this->disk('out', $url);
$data['value'] = (isset($data['value'])) ? abs($data['value']) : 0;
$data['percent'] = (isset($data['percent'])) ? abs($data['percent']) : 0;
break;
case 'cpu':
$data = $this->cpu($url);
break;
case 'net-in':
$data = $this->net('received', $url);
break;
case 'net-out':
$data = $this->net('sent', $url);
$data['value'] = (isset($data['value'])) ? abs($data['value']) : 0;
$data['percent'] = (isset($data['percent'])) ? abs($data['percent']) : 0;
break;
case 'ram-used':
$data = $this->ram($url);
break;
case 'swap-used':
$data = $this->swap($url);
break;
case 'disk-avail':
$data = $this->diskSpace('avail', $url);
break;
case 'disk-used':
$data = $this->diskSpace('used', $url);
break;
case 'ipmi-temp-c':
$data = $this->ipmiTemp($url, 'c');
break;
case 'ipmi-temp-f':
$data = $this->ipmiTemp($url, 'f');
break;
case 'cpu-temp-c':
$data = $this->cpuTemp($url, 'c');
break;
case 'cpu-temp-f':
$data = $this->cpuTemp($url, 'f');
break;
case 'custom':
$data = $this->customNetdata($url, $i);
break;
default:
$data = [
'title' => 'DNC',
'value' => 0,
'units' => 'N/A',
'max' => 100,
];
break;
}
$data['title'] = $this->config['netdata' . $i . 'Title'];
$data['colour'] = $this->config['netdata' . $i . 'Colour'];
$data['chart'] = $this->config['netdata' . $i . 'Chart'];
$data['size'] = $this->config['netdata' . $i . 'Size'];
$data['lg'] = $this->config['netdata' . ($i) . 'lg'];
$data['md'] = $this->config['netdata' . ($i) . 'md'];
$data['sm'] = $this->config['netdata' . ($i) . 'sm'];
array_push($api['data'], $data);
}
}
$api = $api ?? false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
public function netdataSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Netdata',
'enabled' => true,
'image' => 'plugins/images/tabs/netdata.png',
'category' => 'Monitor',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageNetdataEnabled'),
$this->settingsOption('auth', 'homepageNetdataAuth'),
],
'Connection' => [
$this->settingsOption('url', 'netdataURL'),
$this->settingsOption('blank'),
$this->settingsOption('disable-cert-check', 'netdataDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'netdataUseCustomCertificate'),
],
]
];
for ($i = 1; $i <= 7; $i++) {
$homepageSettings['settings']['Chart ' . $i] = array(
$this->settingsOption('enable', 'netdata' . $i . 'Enabled'),
$this->settingsOption('blank'),
$this->settingsOption('input', 'netdata' . $i . 'Title', ['label' => 'Title', 'help' => 'Title for the netdata graph']),
$this->settingsOption('select', 'netdata' . $i . 'Data', ['label' => 'Data', 'options' => $this->netdataOptions()]),
$this->settingsOption('select', 'netdata' . $i . 'Chart', ['label' => 'Chart', 'options' => $this->netdataChartOptions()]),
$this->settingsOption('select', 'netdata' . $i . 'Colour', ['label' => 'Colour', 'options' => $this->netdataColourOptions()]),
$this->settingsOption('select', 'netdata' . $i . 'Size', ['label' => 'Size', 'options' => $this->netdataSizeOptions()]),
$this->settingsOption('blank'),
$this->settingsOption('switch', 'netdata' . $i . 'lg', ['label' => 'Show on large screens']),
$this->settingsOption('switch', 'netdata' . $i . 'md', ['label' => 'Show on medium screens']),
$this->settingsOption('switch', 'netdata' . $i . 'sm', ['label' => 'Show on small screens']),
);
}
$homepageSettings['settings']['Custom data'] = array(
$this->settingsOption('html', null, ['label' => '', 'override' => 12, 'html' => '
<div>
<p>This is where you can define custom data sources for your netdata charts. To use a custom source, you need to select "Custom" in the data field for the chart.</p>
<p>To define a custom data source, you need to add an entry to the JSON below, where the key is the chart number you want the custom data to be used for. Here is an example to set chart 1 custom data source to RAM percentage:</p>
<pre>
{
"1": {
"url": "/api/v1/data?chart=system.ram&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used|buffers|active|wired",
"value": "result,0",
"units": "%",
"max": 100
}
}
</pre>
<p>The URL is appended to your netdata URL and returns JSON formatted data. The value field tells Organizr how to return the value you want from the netdata API. This should be formatted as comma-separated keys to access the desired value.</p>
<table class="table table-striped">
<thead>
<tr>
<th>Parameter</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody>
<tr>
<td>url</td>
<td>Specifies the netdata API endpoint</td>
<td><i class="fa fa-check text-success" aria-hidden="true"></i></td>
</tr>
<tr>
<td>value</td>
<td>Specifies the selector used to get the data form the netdata response</td>
<td><i class="fa fa-check text-success" aria-hidden="true"></i></td>
</tr>
<tr>
<td>units</td>
<td>Specifies the units shown in the graph/chart. Defaults to %</td>
<td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
</tr>
<tr>
<td>max</td>
<td>Specifies the maximum possible value for the data. Defaults to 100</td>
<td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
</tr>
<tr>
<td>mutator</td>
<td>Used to perform simple mathematical operations on the result (+, -, /, *). For example: dividing the result by 1000 would be "/1000". These operations can be chained together by putting them in a comma-seprated format.</td>
<td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
</tr>
<tr>
<td>netdata</td>
<td>Can be used to override the netdata instance data is retrieved from (in the format: http://IP:PORT)</td>
<td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
</tr>
</tbody>
</table>
</div>']),
$this->settingsOption('html', 'netdataCustomTextAce', ['class' => 'jsonTextarea hidden', 'label' => 'Custom definitions', 'override' => 12, 'html' => '<div id="netdataCustomTextAce" style="height: 300px;">' . htmlentities($this->config['netdataCustom']) . '</div>']),
$this->settingsOption('textbox', 'netdataCustom', ['class' => 'jsonTextarea hidden', 'id' => 'netdataCustomText', 'label' => '']),
);
$homepageSettings['settings']['Options'] = array(
$this->settingsOption('refresh', 'homepageNetdataRefresh'),
);
return array_merge($homepageInformation, $homepageSettings);
}
public function netdataHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageNetdataEnabled'
],
'auth' => [
'homepageNetdataAuth'
],
'not_empty' => [
'netdataURL'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderNetdata()
{
if ($this->homepageItemPermissions($this->netdataHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Netdata...</h2></div>
<script>
// Netdata
homepageNetdata("' . $this->config['homepageNetdataRefresh'] . '");
// End Netdata
</script>
</div>
';
}
}
public function disk($dimension, $url)
{
$data = [];
// Get Data
$dataUrl = $url . '/api/v1/data?chart=system.io&dimensions=' . $dimension . '&format=array&points=540&group=average&gtime=0&options=absolute|jsonwrap|nonzero&after=-540';
try {
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
$response = Requests::get($dataUrl, [], $options);
if ($response->success) {
$json = json_decode($response->body, true);
$data['value'] = $json['latest_values'][0] / 1000;
$data['percent'] = $this->getPercent($json['latest_values'][0], $json['max']);
$data['units'] = 'MiB/s';
$data['max'] = $json['max'];
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Netdata')->error($e);
};
return $data;
}
public function diskSpace($dimension, $url)
{
$data = [];
// Get Data
$dataUrl = $url . '/api/v1/data?chart=disk_space._&format=json&points=509&group=average&gtime=0&options=ms|jsonwrap|nonzero&after=-540&dimension=' . $dimension;
try {
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
$response = Requests::get($dataUrl, [], $options);
if ($response->success) {
$json = json_decode($response->body, true);
$data['value'] = $json['result']['data'][0][1];
$data['percent'] = $data['value'];
$data['units'] = '%';
$data['max'] = 100;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Netdata')->error($e);
}
return $data;
}
public function net($dimension, $url)
{
$data = [];
// Get Data
$dataUrl = $url . '/api/v1/data?chart=system.net&dimensions=' . $dimension . '&format=array&points=540&group=average&gtime=0&options=absolute|jsonwrap|nonzero&after=-540';
try {
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
$response = Requests::get($dataUrl, [], $options);
if ($response->success) {
$json = json_decode($response->body, true);
$data['value'] = $json['latest_values'][0] / 1000;
$data['percent'] = $this->getPercent($json['latest_values'][0], $json['max']);
$data['units'] = 'Mbit/s';
$data['max'] = $json['max'];
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Netdata')->error($e);
}
return $data;
}
public function cpu($url)
{
$data = [];
$dataUrl = $url . '/api/v1/data?chart=system.cpu&format=array';
try {
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
$response = Requests::get($dataUrl, [], $options);
if ($response->success) {
$json = json_decode($response->body, true);
$data['value'] = $json[0];
$data['percent'] = $data['value'];
$data['max'] = 100;
$data['units'] = '%';
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Netdata')->error($e);
}
return $data;
}
public function ram($url)
{
$data = [];
$dataUrl = $url . '/api/v1/data?chart=system.ram&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used|buffers|active|wired';
try {
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
$response = Requests::get($dataUrl, [], $options);
if ($response->success) {
$json = json_decode($response->body, true);
$data['value'] = $json['result'][0];
$data['percent'] = $data['value'];
$data['max'] = 100;
$data['units'] = '%';
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Netdata')->error($e);
}
return $data;
}
public function swap($url)
{
$data = [];
$dataUrl = $url . '/api/v1/data?chart=system.swap&format=array&points=540&group=average&gtime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used';
try {
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
$response = Requests::get($dataUrl, [], $options);
if ($response->success) {
$json = json_decode($response->body, true);
$data['value'] = $json['result'][0];
$data['percent'] = $data['value'];
$data['max'] = 100;
$data['units'] = '%';
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Netdata')->error($e);
}
return $data;
}
public function getPercent($val, $max)
{
if ($max == 0) {
return 0;
} else {
return ($val / $max) * 100;
}
}
public function customNetdata($url, $id)
{
try {
$customs = json_decode($this->config['netdataCustom'], true, 512, JSON_THROW_ON_ERROR);
} catch (Exception $e) {
$customs = false;
}
if ($customs == false) {
return [
'error' => 'unable to parse custom JSON'
];
} else if (!isset($customs[$id])) {
return [
'error' => 'custom definition not found'
];
} else {
$data = [];
$custom = $customs[$id];
if (isset($custom['url']) && isset($custom['value'])) {
if (isset($custom['netdata']) && $custom['netdata'] != '') {
$url = $this->qualifyURL($custom['netdata']);
}
$dataUrl = $url . '/' . $custom['url'];
try {
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
$response = Requests::get($dataUrl, [], $options);
if ($response->success) {
$json = json_decode($response->body, true);
if (!isset($custom['max']) || $custom['max'] == '') {
$custom['max'] = 100;
}
$data['max'] = $custom['max'];
if (!isset($custom['units']) || $custom['units'] == '') {
$custom['units'] = '%';
}
$data['units'] = $custom['units'];
$selectors = explode(',', $custom['value']);
foreach ($selectors as $selector) {
if (is_numeric($selector)) {
$selector = (int)$selector;
}
if (!isset($data['value'])) {
$data['value'] = $json[$selector];
} else {
$data['value'] = $data['value'][$selector];
}
}
if (isset($custom['mutator'])) {
$data['value'] = $this->parseMutators($data['value'], $custom['mutator']);
}
if ($data['max'] == 0) {
$data['percent'] = 0;
} else {
$data['percent'] = ($data['value'] / $data['max']) * 100;
}
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Netdata')->error($e);
}
} else {
$data['error'] = 'custom definition incomplete';
}
return $data;
}
}
public function parseMutators($val, $mutators)
{
$mutators = explode(',', $mutators);
foreach ($mutators as $m) {
$op = $m[0];
try {
$m = (float)substr($m, 1);
switch ($op) {
case '+':
$val = $val + $m;
break;
case '-':
$val = $val - $m;
break;
case '/':
$val = $val / $m;
break;
case '*':
$val = $val * $m;
break;
default:
break;
}
} catch (Exception $e) {
//
}
}
return $val;
}
}

View File

@@ -0,0 +1,148 @@
<?php
trait NZBGetHomepageItem
{
public function nzbgetSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'NZBGet',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/nzbget.png',
'category' => 'Downloader',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageNzbgetEnabled'),
$this->settingsOption('auth', 'homepageNzbgetAuth'),
],
'Connection' => [
$this->settingsOption('url', 'nzbgetURL'),
$this->settingsOption('blank'),
$this->settingsOption('username', 'nzbgetUsername'),
$this->settingsOption('password', 'nzbgetPassword'),
$this->settingsOption('disable-cert-check', 'nzbgetDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'nzbgetUseCustomCertificate'),
],
'API SOCKS' => [
$this->settingsOption('socks', 'nzbget'),
$this->settingsOption('blank'),
$this->settingsOption('enable', 'nzbgetSocksEnabled'),
$this->settingsOption('auth', 'nzbgetSocksAuth'),
],
'Misc Options' => [
$this->settingsOption('refresh', 'nzbgetRefresh'),
$this->settingsOption('combine', 'nzbgetCombine'),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'nzbget'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionNZBGet()
{
if (empty($this->config['nzbgetURL'])) {
$this->setAPIResponse('error', 'NZBGet URL is not defined', 422);
return false;
}
try {
$url = $this->qualifyURL($this->config['nzbgetURL']);
$options = $this->requestOptions($url, null, $this->config['nzbgetDisableCertCheck'], $this->config['nzbgetUseCustomCertificate']);
$urlGroups = $url . '/jsonrpc/listgroups';
if ($this->config['nzbgetUsername'] !== '' && $this->decrypt($this->config['nzbgetPassword']) !== '') {
$credentials = array('auth' => new Requests_Auth_Basic(array($this->config['nzbgetUsername'], $this->decrypt($this->config['nzbgetPassword']))));
$options = array_merge($options, $credentials);
}
$response = Requests::get($urlGroups, array(), $options);
if ($response->success) {
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
} else {
$this->setAPIResponse('success', 'NZBGet: An Error Occurred', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('NZBGet')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function nzbgetHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageNzbgetEnabled'
],
'auth' => [
'homepageNzbgetAuth'
],
'not_empty' => [
'nzbgetURL'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrdernzbget()
{
if ($this->homepageItemPermissions($this->nzbgetHomepagePermissions('main'))) {
$loadingBox = ($this->config['nzbgetCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
$builder = ($this->config['nzbgetCombine']) ? 'buildDownloaderCombined(\'nzbget\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("nzbget"));';
return '
<div id="' . __FUNCTION__ . '">
' . $loadingBox . '
<script>
// homepageOrdernzbget
' . $builder . '
homepageDownloader("nzbget", "' . $this->config['nzbgetRefresh'] . '");
// End homepageOrdernzbget
</script>
</div>
';
}
}
public function getNzbgetHomepageQueue()
{
if (!$this->homepageItemPermissions($this->nzbgetHomepagePermissions('main'), true)) {
return false;
}
try {
$url = $this->qualifyURL($this->config['nzbgetURL']);
$options = $this->requestOptions($url, $this->config['nzbgetRefresh'], $this->config['nzbgetDisableCertCheck'], $this->config['nzbgetUseCustomCertificate']);
$urlGroups = $url . '/jsonrpc/listgroups';
$urlHistory = $url . '/jsonrpc/history';
if ($this->config['nzbgetUsername'] !== '' && $this->decrypt($this->config['nzbgetPassword']) !== '') {
$credentials = array('auth' => new Requests_Auth_Basic(array($this->config['nzbgetUsername'], $this->decrypt($this->config['nzbgetPassword']))));
$options = array_merge($options, $credentials);
}
$response = Requests::get($urlGroups, array(), $options);
if ($response->success) {
$api['content']['queueItems'] = json_decode($response->body, true);
}
$response = Requests::get($urlHistory, array(), $options);
if ($response->success) {
$api['content']['historyItems'] = json_decode($response->body, true);
}
$api['content'] = isset($api['content']) ? $api['content'] : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
} catch (Requests_Exception $e) {
$this->setLoggerChannel('NZBGet')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,110 @@
<?php
trait OctoPrintHomepageItem
{
public function octoprintSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Octoprint',
'enabled' => true,
'image' => 'plugins/images/tabs/octoprint.png',
'category' => 'Monitor',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageOctoprintEnabled'),
$this->settingsOption('auth', 'homepageOctoprintAuth'),
],
'Connection' => [
$this->settingsOption('url', 'octoprintURL'),
$this->settingsOption('token', 'octoprintToken'),
$this->settingsOption('disable-cert-check', 'octoprintDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'octoprintUseCustomCertificate'),
],
'Options' => [
$this->settingsOption('title', 'octoprintHeader'),
$this->settingsOption('toggle-title', 'octoprintHeaderToggle'),
],
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function octoprintHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageOctoprintEnabled'
],
'auth' => [
'homepageOctoprintAuth'
],
'not_empty' => [
'octoprintURL',
'octoprintToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderOctoprint()
{
if ($this->homepageItemPermissions($this->octoprintHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading OctoPrint...</h2></div>
<script>
// Octoprint
homepageOctoprint("' . $this->config['homepageOctoprintRefresh'] . '");
// End Octoprint
</script>
</div>
';
}
}
public function getOctoprintHomepageData()
{
if (!$this->homepageItemPermissions($this->octoprintHomepagePermissions('main'), true)) {
return false;
}
$api = [];
$url = $this->qualifyURL($this->config['octoprintURL']);
$endpoints = ['job', 'settings'];
$api['data']['url'] = $this->config['octoprintURL'];
foreach ($endpoints as $endpoint) {
$dataUrl = $url . '/api/' . $endpoint;
try {
$headers = array('X-API-KEY' => $this->config['octoprintToken']);
$options = $this->requestOptions($url, $this->config['homepageOctoprintRefresh'], $this->config['octoprintDisableCertCheck'], $this->config['octoprintUseCustomCertificate']);
$response = Requests::get($dataUrl, $headers, $options);
if ($response->success) {
$json = json_decode($response->body, true);
$api['data'][$endpoint] = $json;
$api['options'] = [
'title' => $this->config['octoprintHeader'],
'titleToggle' => $this->config['octoprintHeaderToggle'],
];
} else {
$this->setAPIResponse('error', 'OctoPrint connection error', 409);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Octoprint')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
};
}
$api = isset($api) ? $api : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
}

View File

@@ -0,0 +1,439 @@
<?php
trait OmbiHomepageItem
{
public function ombiSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Ombi',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/ombi.png',
'category' => 'Requests',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageOmbiEnabled'),
$this->settingsOption('auth', 'homepageOmbiAuth'),
],
'Connection' => [
$this->settingsOption('url', 'ombiURL'),
$this->settingsOption('token', 'ombiToken'),
$this->settingsOption('username', 'ombiFallbackUser', ['label' => 'Ombi Fallback User', 'help' => 'Organizr will request an Ombi User Token based off of this user credentials']),
$this->settingsOption('password', 'ombiFallbackPassword', ['label' => 'Ombi Fallback Password',]),
$this->settingsOption('disable-cert-check', 'ombiDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'ombiUseCustomCertificate'),
],
'Misc Options' => [
$this->settingsOption('auth', 'homepageOmbiRequestAuth', ['label' => 'Minimum Group to Request']),
$this->settingsOption('select', 'ombiTvDefault', ['label' => 'TV Show Default Request', 'options' => $this->requestTvOptions()]),
$this->settingsOption('switch', 'ombiLimitUser', ['label' => 'Limit to User']),
$this->settingsOption('limit', 'ombiLimit'),
$this->settingsOption('refresh', 'ombiRefresh'),
$this->settingsOption('switch', 'ombiAlias', ['label' => 'Use Ombi Alias Names', 'help' => 'Use Ombi Alias Names instead of Usernames - If Alias is blank, Alias will fallback to Username']),
],
'Default Filter' => [
$this->settingsOption('switch', 'ombiDefaultFilterAvailable', ['label' => 'Show Available', 'help' => 'Show All Available Ombi Requests']),
$this->settingsOption('switch', 'ombiDefaultFilterUnavailable', ['label' => 'Show Unavailable', 'help' => 'Show All Unavailable Ombi Requests']),
$this->settingsOption('switch', 'ombiDefaultFilterApproved', ['label' => 'Show Approved', 'help' => 'Show All Approved Ombi Requests']),
$this->settingsOption('switch', 'ombiDefaultFilterUnapproved', ['label' => 'Show Unapproved', 'help' => 'Show All Unapproved Ombi Requests']),
$this->settingsOption('switch', 'ombiDefaultFilterDenied', ['label' => 'Show Denied', 'help' => 'Show All Denied Ombi Requests']),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'ombi'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionOmbi()
{
if (empty($this->config['ombiURL'])) {
$this->setAPIResponse('error', 'Ombi URL is not defined', 422);
return false;
}
if (empty($this->config['ombiToken'])) {
$this->setAPIResponse('error', 'Ombi Token is not defined', 422);
return false;
}
$headers = array(
"Accept" => "application/json",
"Apikey" => $this->config['ombiToken'],
);
$url = $this->qualifyURL($this->config['ombiURL']);
try {
$options = $this->requestOptions($url, null, $this->config['ombiDisableCertCheck'], $this->config['ombiUseCustomCertificate']);
$test = Requests::get($url . "/api/v1/Settings/about", $headers, $options);
if ($test->success) {
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
} else {
$this->setResponse(401, $test->body);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Ombi')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function ombiHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageOmbiEnabled'
],
'auth' => [
'homepageOmbiAuth'
],
'not_empty' => [
'ombiURL',
'ombiToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderombi()
{
if ($this->homepageItemPermissions($this->ombiHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Requests...</h2></div>
<script>
// Ombi Requests
homepageRequests("ombi", "' . $this->config['ombiRefresh'] . '");
// End Ombi Requests
</script>
</div>
';
}
}
public function getOmbiRequests($type = "both", $limit = 50, $offset = 0)
{
if (!$this->homepageItemPermissions($this->ombiHomepagePermissions('main'), true)) {
return false;
}
$api['count'] = array(
'movie' => 0,
'tv' => 0,
'limit' => (integer)$limit,
'offset' => (integer)$offset
);
$headers = array(
"Accept" => "application/json",
"Apikey" => $this->config['ombiToken'],
);
$requests = array();
$url = $this->qualifyURL($this->config['ombiURL']);
try {
$options = $this->requestOptions($url, $this->config['ombiRefresh'], $this->config['ombiDisableCertCheck'], $this->config['ombiUseCustomCertificate']);
switch ($type) {
case 'movie':
$movie = Requests::get($url . "/api/v1/Request/movie", $headers, $options);
break;
case 'tv':
$tv = Requests::get($url . "/api/v1/Request/tv", $headers, $options);
break;
default:
$movie = Requests::get($url . "/api/v1/Request/movie", $headers, $options);
$tv = Requests::get($url . "/api/v1/Request/tv", $headers, $options);
break;
}
if ($movie->success || $tv->success) {
if (isset($movie)) {
$movie = json_decode($movie->body, true);
//$movie = array_reverse($movie);
foreach ($movie as $key => $value) {
$proceed = (($this->config['ombiLimitUser']) && strtolower($this->user['username']) == strtolower($value['requestedUser']['userName'])) || (strtolower($value['requestedUser']['userName']) == strtolower($this->config['ombiFallbackUser'])) || (!$this->config['ombiLimitUser']) || $this->qualifyRequest(1);
if ($proceed) {
$api['count']['movie']++;
$requests[] = array(
'id' => $value['theMovieDbId'],
'title' => $value['title'],
'overview' => $value['overview'],
'poster' => (isset($value['posterPath']) && $value['posterPath'] !== '') ? 'https://image.tmdb.org/t/p/w300/' . $value['posterPath'] : 'plugins/images/homepage/no-list.png',
'background' => (isset($value['background']) && $value['background'] !== '') ? 'https://image.tmdb.org/t/p/w1280/' . $value['background'] : '',
'approved' => $value['approved'],
'available' => $value['available'],
'denied' => $value['denied'],
'deniedReason' => $value['deniedReason'],
'user' => $value['requestedUser']['userName'],
'userAlias' => $value['requestedUser']['userAlias'],
'request_id' => $value['id'],
'request_date' => $value['requestedDate'],
'release_date' => $value['releaseDate'],
'type' => 'movie',
'icon' => 'mdi mdi-filmstrip',
'color' => 'palette-Deep-Purple-900 bg white',
);
}
}
}
if (isset($tv) && (is_array($tv) || is_object($tv))) {
$tv = json_decode($tv->body, true);
foreach ($tv as $key => $value) {
if (count($value['childRequests']) > 0) {
$proceed = (($this->config['ombiLimitUser']) && strtolower($this->user['username']) == strtolower($value['childRequests'][0]['requestedUser']['userName'])) || (!$this->config['ombiLimitUser']) || $this->qualifyRequest(1);
if ($proceed) {
$api['count']['tv']++;
$requests[] = array(
'id' => $value['tvDbId'],
'title' => $value['title'],
'overview' => $value['overview'],
'poster' => (isset($value['posterPath']) && $value['posterPath'] !== '') ? (str_starts_with($value['posterPath'], '/') ? 'https://image.tmdb.org/t/p/w300/' . $value['posterPath'] : $value['posterPath']) : 'plugins/images/homepage/no-list.png',
'background' => (isset($value['background']) && $value['background'] !== '') ? 'https://image.tmdb.org/t/p/w1280/' . $value['background'] : '',
'approved' => $value['childRequests'][0]['approved'],
'available' => $value['childRequests'][0]['available'],
'denied' => $value['childRequests'][0]['denied'],
'deniedReason' => $value['childRequests'][0]['deniedReason'],
'user' => $value['childRequests'][0]['requestedUser']['userName'],
'userAlias' => $value['childRequests'][0]['requestedUser']['userAlias'],
'request_id' => $value['id'],
'request_date' => $value['childRequests'][0]['requestedDate'],
'release_date' => $value['releaseDate'],
'type' => 'tv',
'icon' => 'mdi mdi-television',
'color' => 'grayish-blue-bg',
);
}
}
}
}
//sort here
usort($requests, function ($item1, $item2) {
if ($item1['request_date'] == $item2['request_date']) {
return 0;
}
return $item1['request_date'] > $item2['request_date'] ? -1 : 1;
});
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Ombi')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = isset($requests) ? array_slice($requests, $offset, $limit) : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
public function addOmbiRequest($id, $type)
{
$id = ($id) ?? null;
$type = ($type) ?? null;
if (!$id) {
$this->setAPIResponse('error', 'Id was not supplied', 422);
return false;
}
if (!$type) {
$this->setAPIResponse('error', 'Type was not supplied', 422);
return false;
}
if (!$this->homepageItemPermissions($this->ombiHomepagePermissions('main'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['ombiURL']);
switch ($type) {
case 'season':
case 'tv':
$type = 'tv';
$add = array(
'tvDbId' => $id,
'requestAll' => $this->ombiTVDefault('all'),
'latestSeason' => $this->ombiTVDefault('last'),
'firstSeason' => $this->ombiTVDefault('first')
);
break;
default:
$type = 'movie';
$add = array("theMovieDbId" => (int)$id);
break;
}
try {
$options = array('timeout' => 30);
if (isset($_COOKIE['Auth'])) {
$headers = array(
"Accept" => "application/json",
"Content-Type" => "application/json",
"Authorization" => "Bearer " . $_COOKIE['Auth']
);
} else {
$this->setAPIResponse('error', 'User does not have Auth Cookie', 500);
return false;
}
//https://api.themoviedb.org/3/movie/157336?api_key=83cf4ee97bb728eeaf9d4a54e64356a1
// Lets check if it exists inside Ombi first... but since I can't search with ID - i have to query title from id
$tmdbResponse = Requests::get('https://api.themoviedb.org/3/' . $type . '/' . $id . '?api_key=83cf4ee97bb728eeaf9d4a54e64356a1', [], $options);
if ($tmdbResponse->success) {
$details = json_decode($tmdbResponse->body, true);
if (count($details) > 0) {
switch ($type) {
case 'tv':
$title = $details['name'];
$idType = 'theTvDbId';
$tmdbResponseID = Requests::get('https://api.themoviedb.org/3/tv/' . $id . '/external_ids?api_key=83cf4ee97bb728eeaf9d4a54e64356a1', [], $options);
if ($tmdbResponseID->success) {
$detailsID = json_decode($tmdbResponseID->body, true);
if (count($detailsID) > 0) {
if (isset($detailsID['tvdb_id'])) {
$id = $detailsID['tvdb_id'];
$add['tvDbId'] = $id;
} else {
$this->setAPIResponse('error', 'Could not get TVDB Id', 422);
return false;
}
} else {
$this->setAPIResponse('error', 'Could not get TVDB Id', 422);
return false;
}
}
break;
case 'movie':
$title = $details['title'];
$idType = 'theMovieDbId';
break;
default:
$this->setAPIResponse('error', 'Ombi Type was not found', 422);
return false;
}
} else {
$this->setAPIResponse('error', 'No data returned from TMDB', 422);
return false;
}
} else {
$this->setAPIResponse('error', 'Could not contact TMDB', 422);
return false;
}
$options = $this->requestOptions($url, null, $this->config['ombiDisableCertCheck'], $this->config['ombiUseCustomCertificate']);
$searchResponse = Requests::get($url . '/api/v1/Search/' . $type . '/' . urlencode($title), $headers, $options);
if ($searchResponse->success) {
$details = json_decode($searchResponse->body, true);
if (count($details) > 0) {
foreach ($details as $k => $v) {
if ($v[$idType] == $id) {
if ($v['available']) {
$this->setAPIResponse('error', 'Request is already available', 409);
return false;
} elseif ($v['requested']) {
$this->setAPIResponse('error', 'Request is already requested', 409);
return false;
}
}
}
}
} else {
$this->setAPIResponse('error', 'Ombi Error Occurred', 500);
return false;
}
$response = Requests::post($url . "/api/v1/Request/" . $type, $headers, json_encode($add), $options);
if ($response->success) {
$this->setAPIResponse('success', 'Ombi Request submitted', 200);
return true;
} else {
$this->setAPIResponse('error', 'Ombi Error Occurred', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Ombi')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function actionOmbiRequest($id, $type, $action)
{
$id = ($id) ?? null;
$type = ($type) ?? null;
$action = ($action) ?? null;
if (!$id) {
$this->setAPIResponse('error', 'Id was not supplied', 422);
return false;
}
if (!$type) {
$this->setAPIResponse('error', 'Type was not supplied', 422);
return false;
}
if (!$action) {
$this->setAPIResponse('error', 'Action was not supplied', 422);
return false;
}
if (!$this->homepageItemPermissions($this->ombiHomepagePermissions('main'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['ombiURL']);
$headers = array(
"Accept" => "application/json",
"Content-Type" => "application/json",
"Apikey" => $this->config['ombiToken']
);
$data = array(
'id' => $id,
);
switch ($type) {
case 'season':
case 'tv':
$type = 'tv';
break;
default:
$type = 'movie';
break;
}
try {
$options = $this->requestOptions($url, 60, $this->config['ombiDisableCertCheck'], $this->config['ombiUseCustomCertificate']);
switch ($action) {
case 'approve':
$response = Requests::post($url . "/api/v1/Request/" . $type . "/approve", $headers, json_encode($data), $options);
$message = 'Ombi Request has been approved';
break;
case 'available':
$response = Requests::post($url . "/api/v1/Request/" . $type . "/available", $headers, json_encode($data), $options);
$message = 'Ombi Request has been marked available';
break;
case 'unavailable':
$response = Requests::post($url . "/api/v1/Request/" . $type . "/unavailable", $headers, json_encode($data), $options);
$message = 'Ombi Request has been marked unavailable';
break;
case 'deny':
$response = Requests::put($url . "/api/v1/Request/" . $type . "/deny", $headers, json_encode($data), $options);
$message = 'Ombi Request has been denied';
break;
case 'delete':
$response = Requests::delete($url . "/api/v1/Request/" . $type . "/" . $id, $headers, $options);
$message = 'Ombi Request has been deleted';
break;
default:
return false;
}
if ($response->success) {
$this->setAPIResponse('success', $message, 200);
return true;
} else {
$this->setAPIResponse('error', 'Ombi Error Occurred', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Ombi')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function ombiTVDefault($type)
{
return $type == $this->config['ombiTvDefault'];
}
}

View File

@@ -0,0 +1,572 @@
<?php
trait OverseerrHomepageItem
{
public function overseerrSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Overseerr',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/overseerr.png',
'category' => 'Requests',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageOverseerrEnabled'),
$this->settingsOption('auth', 'homepageOverseerrAuth'),
$this->settingsOption('notice', '', ['title' => 'Attention', 'body' => 'Since Organizr supports multiple Request Providers, You must now select which service you want to submit requests through']),
$this->settingsOption('select', 'defaultRequestService', ['label' => 'Default Request Service', 'options' => $this->requestServiceOptions()]),
],
'Connection' => [
$this->settingsOption('url', 'overseerrURL'),
$this->settingsOption('token', 'overseerrToken'),
$this->settingsOption('username', 'overseerrFallbackUser', ['label' => 'Overseerr Fallback User', 'help' => 'Organizr will request an Overseerr User Token based off of this user credentials']),
$this->settingsOption('password', 'overseerrFallbackPassword', ['label' => 'Overseerr Fallback Password',]),
$this->settingsOption('disable-cert-check', 'overseerrDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'overseerrUseCustomCertificate'),
],
'Misc Options' => [
$this->settingsOption('auth', 'homepageOverseerrRequestAuth', ['label' => 'Minimum Group to Request']),
$this->settingsOption('select', 'overseerrTvDefault', ['label' => 'TV Show Default Request', 'options' => $this->requestTvOptions(true)]),
$this->settingsOption('switch', 'overseerrLimitUser', ['label' => 'Limit to User']),
$this->settingsOption('limit', 'overseerrLimit'),
$this->settingsOption('switch', 'overseerrPrefer4K', ['label' => 'Prefer 4K Server']),
$this->settingsOption('refresh', 'overseerrRefresh'),
],
'Default Filter' => [
$this->settingsOption('switch', 'overseerrDefaultFilterAvailable', ['label' => 'Show Available', 'help' => 'Show All Available Overseerr Requests']),
$this->settingsOption('switch', 'overseerrDefaultFilterUnavailable', ['label' => 'Show Unavailable', 'help' => 'Show All Unavailable Overseerr Requests']),
$this->settingsOption('switch', 'overseerrDefaultFilterApproved', ['label' => 'Show Approved', 'help' => 'Show All Approved Overseerr Requests']),
$this->settingsOption('switch', 'overseerrDefaultFilterUnapproved', ['label' => 'Show Unapproved', 'help' => 'Show All Unapproved Overseerr Requests']),
$this->settingsOption('switch', 'overseerrDefaultFilterDenied', ['label' => 'Show Denied', 'help' => 'Show All Denied Overseerr Requests']),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'overseerr'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionOverseerr()
{
if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('test'), true)) {
return false;
}
$headers = array(
"Accept" => "application/json",
"X-Api-Key" => $this->config['overseerrToken'],
);
$url = $this->qualifyURL($this->config['overseerrURL']);
try {
$options = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
$test = Requests::get($url . "/api/v1/settings/main", $headers, $options);
$testData = json_decode($test->body, true);
if ($test->success && isset($testData["apiKey"]) && $testData["apiKey"] == $this->config['overseerrToken']) {
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
} else {
$this->setResponse(401, 'API Connection failed');
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Overseerr')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function overseerrHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageOverseerrEnabled'
],
'auth' => [
'homepageOverseerrAuth'
],
'not_empty' => [
'overseerrURL',
'overseerrToken'
]
],
'request' => [
'enabled' => [
'homepageOverseerrEnabled'
],
'auth' => [
'homepageOverseerrAuth',
'homepageOverseerrRequestAuth'
],
'not_empty' => [
'overseerrURL',
'overseerrToken'
]
],
'test' => [
'not_empty' => [
'overseerrURL',
'overseerrToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderoverseerr()
{
if ($this->homepageItemPermissions($this->overseerrHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Requests...</h2></div>
<script>
// Overseerr Requests
homepageRequests("overseerr", "' . $this->config['overseerrRefresh'] . '");
// End Overseerr Requests
</script>
</div>
';
}
}
public function getOverseerrRequests($limit = 50, $offset = 0)
{
if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('main'), true)) {
return false;
}
$limit = is_numeric($limit) ? (int)$limit : 50;
$offset = is_numeric($offset) ? (int)$offset : 0;
$api['count'] = [
'movie' => 0,
'tv' => 0,
'limit' => $limit,
'offset' => $offset
];
$headers = [
"Accept" => "application/json",
"X-Api-Key" => $this->config['overseerrToken'],
];
$requests = [];
$url = $this->qualifyURL($this->config['overseerrURL']);
try {
$options = $this->requestOptions($url, $this->config['overseerrRefresh'], $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
$request = Requests::get($url . "/api/v1/request?take=" . $limit . '&skip=' . $offset, $headers, $options);
if ($request->success) {
$requestAll = [];
$requestsData = json_decode($request->body, true);
foreach ($requestsData['results'] as $key => $value) {
$requester = ($value['requestedBy']['username'] !== '' && $value['requestedBy']['username'] !== null) ? $value['requestedBy']['username'] : $value['requestedBy']['plexUsername'];
$requesterEmail = $value['requestedBy']['email'];
$proceed = (($this->config['overseerrLimitUser']) && strtolower($this->user['username']) == strtolower($requester)) || (strtolower($requester) == strtolower($this->config['overseerrFallbackUser'])) || (!$this->config['overseerrLimitUser']) || $this->qualifyRequest(1);
if ($proceed) {
$requestAll[$value['media']['tmdbId']] = [
'url' => $url . '/api/v1/' . $value['type'] . '/' . $value['media']['tmdbId'],
'headers' => $headers,
'type' => Requests::GET,
];
$api['count'][$value['type']]++;
$requests[$value['media']['tmdbId']] = [
'id' => $value['media']['tmdbId'],
'approved' => $value['status'] == 2,
'available' => $value['media']['status'] == 5,
'denied' => $value['status'] == 3,
'deniedReason' => 'n/a',
'user' => $requester,
'userAlias' => $value['requestedBy']['displayName'],
'request_id' => $value['id'],
'request_date' => $value['createdAt'],
'type' => $value['type'],
'icon' => 'mdi mdi-' . ($value['type'] == 'movie') ? 'filmstrip' : 'television',
'color' => ($value['type'] == 'movie') ? 'palette-Deep-Purple-900 bg white' : 'grayish-blue-bg',
];
/* OLD WAY
$requestItem = Requests::get($url . '/api/v1/' . $value['type'] . '/' . $value['media']['tmdbId'], $headers, $options);
$requestsItemData = json_decode($requestItem->body, true);
if ($requestItem->success) {
$api['count'][$value['type']]++;
$requests[] = array(
'id' => $value['media']['tmdbId'],
'title' => ($value['type'] == 'movie') ? $requestsItemData['title'] : $requestsItemData['name'],
'overview' => $requestsItemData['overview'],
'poster' => (isset($requestsItemData['posterPath']) && $requestsItemData['posterPath'] !== '') ? 'https://image.tmdb.org/t/p/w300/' . $requestsItemData['posterPath'] : 'plugins/images/homepage/no-list.png',
'background' => (isset($requestsItemData['backdropPath']) && $requestsItemData['backdropPath'] !== '') ? 'https://image.tmdb.org/t/p/w1280/' . $requestsItemData['backdropPath'] : '',
'approved' => $value['status'] == 2,
'available' => $value['media']['status'] == 5,
'denied' => $value['status'] == 3,
'deniedReason' => 'n/a',
'user' => $requester,
'userAlias' => $value['requestedBy']['displayName'],
'request_id' => $value['id'],
'request_date' => $value['createdAt'],
'release_date' => ($value['type'] == 'movie') ? $requestsItemData['releaseDate'] : $requestsItemData['firstAirDate'],
'type' => $value['type'],
'icon' => 'mdi mdi-' . ($value['type'] == 'movie') ? 'filmstrip' : 'television',
'color' => ($value['type'] == 'movie') ? 'palette-Deep-Purple-900 bg white' : 'grayish-blue-bg',
);
}*/
}
}
$requestItems = Requests::request_multiple($requestAll, $options);
foreach ($requestItems as $key => $requestedItem) {
if ($requestedItem->success) {
$requestsItemData = json_decode($requestedItem->body, true);
$requests[$key]['title'] = $requestsItemData['title'] ?? $requestsItemData['name'];
$requests[$key]['release_date'] = $requestsItemData['releaseDate'] ?? $requestsItemData['firstAirDate'];
$requests[$key]['background'] = (isset($requestsItemData['backdropPath']) && $requestsItemData['backdropPath'] !== '') ? 'https://image.tmdb.org/t/p/w1280/' . $requestsItemData['backdropPath'] : '';
$requests[$key]['poster'] = (isset($requestsItemData['posterPath']) && $requestsItemData['posterPath'] !== '') ? 'https://image.tmdb.org/t/p/w300/' . $requestsItemData['posterPath'] : 'plugins/images/homepage/no-list.png';
$requests[$key]['overview'] = $requestsItemData['overview'];
} else {
unset($requests[$key]);
}
}
//sort here
usort($requests, function ($item1, $item2) {
if ($item1['request_date'] == $item2['request_date']) {
return 0;
}
return $item1['request_date'] > $item2['request_date'] ? -1 : 1;
});
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Overseerr')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
$api['content'] = $requests ?? false;
$this->setResponse(200, null, $api);
return $api;
}
public function getDefaultService($services)
{
if (empty($services)) {
return null;
} else {
$default = false;
foreach ($services as $key => $service) {
if ($service['isDefault']) {
if ($service['is4k']) {
if ($this->config['overseerrPrefer4K']) {
$default = (int)$key;
}
} else {
if (!$this->config['overseerrPrefer4K']) {
$default = (int)$key;
}
}
}
}
return ($default) ? $services[$default] : $services[0];
}
}
public function addOverseerrRequest($id, $type, $seasons = null)
{
$id = ($id) ?? null;
$type = ($type) ?? null;
if (!$id) {
$this->setAPIResponse('error', 'Id was not supplied', 422);
return false;
}
if (!$type) {
$this->setAPIResponse('error', 'Type was not supplied', 422);
return false;
}
if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('main'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['overseerrURL']);
try {
if (!isset($_COOKIE['connect_sid']) && !isset($_COOKIE['connect.sid'])) {
$this->setAPIResponse('error', 'User does not have Auth Cookie', 500);
return false;
}
$headers = array(
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Api-Key' => $this->config['overseerrToken']
);
$cookieJar = new Requests_Cookie_Jar(['connect.sid' => $_COOKIE['connect_sid']]);
$optionsUser = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate'], ['cookies' => $cookieJar]);
$optionsAPI = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
// Check if requested already
$searchResponse = Requests::get($url . '/api/v1/request/', $headers, $optionsAPI);
if ($searchResponse->success) {
$details = json_decode($searchResponse->body, true);
if (count($details['results']) > 0) {
foreach ($details['results'] as $k => $v) {
if ($v['media']['tmdbId'] == $id) {
if ($v['media']['status'] == 5) {
$this->setAPIResponse('error', 'Request is already available', 409);
return false;
} else {
$this->setAPIResponse('error', 'Request is already requested', 409);
return false;
}
}
}
}
} else {
$this->setAPIResponse('error', 'Overseerr Connection Error Occurred', 500);
return false;
}
// Get User info
$response = Requests::get($url . '/api/v1/auth/me', [], $optionsUser);
if ($response->success) {
$userInfo = json_decode($response->body, true);
} else {
$this->setResponse(500, 'Error getting user information');
return false;
}
switch ($type) {
case 'season':
case 'tv':
if ($this->config['overseerrTvDefault'] == 'user') {
if ($seasons) {
if (strpos($seasons, ',') !== false) {
$seasonsExplode = explode(',', $seasons);
foreach ($seasonsExplode as $season) {
$seasonsArray[] = (int)$season;
}
$seasons = $seasonsArray;
} else {
$seasonsArray[] = (int)$seasons;
$seasons = $seasonsArray;
}
} else {
$this->setResponse(500, 'Seasons requested was not supplied');
return false;
}
}
$response = Requests::get($url . '/api/v1/tv/' . $id, $headers, $optionsAPI);
if ($response->success) {
$seriesInfo = json_decode($response->body, true);
} else {
$this->setResponse(500, 'Error getting series information');
return false;
}
if ($this->config['overseerrTvDefault'] == 'first') {
$seasons = [1];
} elseif ($this->config['overseerrTvDefault'] == 'last') {
$lastSeason = end($seriesInfo['seasons']);
$lastSeasonNumber = $lastSeason['seasonNumber'];
$seasons = [$lastSeasonNumber];
} elseif ($this->config['overseerrTvDefault'] == 'all') {
$seasons = [];
foreach ($seriesInfo['seasons'] as $season) {
if ($season['seasonNumber'] !== 0) {
$seasons[] = $season['seasonNumber'];
}
}
}
$response = Requests::get($url . '/api/v1/service/sonarr', $headers, $optionsAPI);
if ($response->success) {
$serviceInfo = $this->getDefaultService(json_decode($response->body, true));
if (!$serviceInfo) {
$this->setResponse(404, 'No Sonarr service was found in Overseerr');
return false;
}
} else {
$this->setResponse(500, 'Error getting service information');
return false;
}
$add = [
'mediaId' => (int)$id,
'tvdbId' => $seriesInfo['externalIds']['tvdbId'],
'mediaType' => 'tv',
'is4k' => (bool)$serviceInfo['is4k'],
'seasons' => $seasons,
'serverId' => (int)$serviceInfo['id'],
'profileId' => (int)$serviceInfo['activeProfileId'],
'rootFolder' => $serviceInfo['activeDirectory'],
'languageProfileId' => (int)$serviceInfo['activeLanguageProfileId'],
//'userId' => (int)$userInfo['id'],
'tags' => []
];
break;
default:
$response = Requests::get($url . '/api/v1/service/radarr', $headers, $optionsAPI);
if ($response->success) {
$serviceInfo = $this->getDefaultService(json_decode($response->body, true));
if (!$serviceInfo) {
$this->setResponse(404, 'No Radarr service was found in Overseerr');
return false;
}
} else {
$this->setResponse(500, 'Error getting service information');
return false;
}
$add = [
'mediaId' => (int)$id,
'mediaType' => 'movie',
'is4k' => (bool)$serviceInfo['is4k'],
'serverId' => (int)$serviceInfo['id'],
'profileId' => (int)$serviceInfo['activeProfileId'],
//'userId' => (int)$userInfo['id'],
'tags' => []
];
break;
}
$response = Requests::post($url . "/api/v1/request", ['Accept' => 'application/json', 'Content-Type' => 'application/json'], json_encode($add), $optionsUser);
if ($response->success) {
$this->setAPIResponse('success', 'Overseerr Request submitted', 200);
return true;
} else {
$message = 'Overseerr Error Occurred';
if ($this->isJSON($response->body)) {
$messageJSON = json_decode($response->body, true);
$message = $messageJSON['message'] ?? $message;
}
$this->setAPIResponse('error', $message, 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Overseerr')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function actionOverseerrRequest($id, $type, $action)
{
$id = ($id) ?? null;
$type = ($type) ?? null;
$action = ($action) ?? null;
if (!$id) {
$this->setAPIResponse('error', 'Id was not supplied', 422);
return false;
}
if (!$type) {
$this->setAPIResponse('error', 'Type was not supplied', 422);
return false;
}
if (!$action) {
$this->setAPIResponse('error', 'Action was not supplied', 422);
return false;
}
if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('main'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['overseerrURL']);
$headers = array(
"Accept" => "application/json",
"Content-Type" => "application/json",
'X-Api-Key' => $this->config['overseerrToken']
);
try {
$options = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
switch ($action) {
case 'approve':
$response = Requests::post($url . "/api/v1/request/" . $id . "/approve", $headers, [], $options);
$message = 'Overseerr Request has been approved';
break;
case 'pending':
$response = Requests::post($url . '/api/v1/request/' . $id . '/pending', $headers, [], $options);
$message = 'Overseerr Request has been approved';
break;
case 'available':
$requestInfoResponse = Requests::get($url . '/api/v1/request/' . $id, $headers, $options);
if ($requestInfoResponse->success) {
$requestInfo = json_decode($requestInfoResponse->body, true);
$mediaId = $requestInfo['media']['id'];
} else {
$this->setResponse(500, 'Error getting request information');
return false;
}
$response = Requests::post($url . '/api/v1/media/' . $mediaId . '/available', $headers, [], $options);
$message = 'Overseerr Request has been marked available';
break;
case 'unavailable':
$requestInfoResponse = Requests::get($url . '/api/v1/request/' . $id, $headers, $options);
if ($requestInfoResponse->success) {
$requestInfo = json_decode($requestInfoResponse->body, true);
$mediaId = $requestInfo['media']['id'];
} else {
$this->setResponse(500, 'Error getting request information');
return false;
}
$response = Requests::post($url . "/api/v1/media/" . $mediaId . "/pending", $headers, [], $options);
$message = 'Overseerr Request has been marked unavailable';
break;
case 'deny':
$response = Requests::post($url . "/api/v1/request/" . $id . "/decline", $headers, [], $options);
$message = 'Overseerr Request has been denied';
break;
case 'delete':
$response = Requests::delete($url . "/api/v1/request/" . $id, $headers, $options);
$message = 'Overseerr Request has been deleted';
break;
default:
return false;
}
if ($response->success) {
$this->setAPIResponse('success', $message, 200);
return true;
} else {
$message = 'Overseerr Error Occurred';
if ($this->isJSON($response->body)) {
$messageJSON = json_decode($response->body, true);
$message = $messageJSON['message'] ?? $message;
}
$this->setAPIResponse('error', $message, 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Overseerr')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function getOverseerrMetadata($id, $type)
{
if (!$id) {
$this->setAPIResponse('error', 'Id was not supplied', 422);
return false;
}
if (!$type) {
$this->setAPIResponse('error', 'Type was not supplied', 422);
return false;
}
if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('request'), true)) {
return false;
}
try {
$url = $this->qualifyURL($this->config['overseerrURL']);
$headers = array(
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Api-Key' => $this->config['overseerrToken']
);
$options = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
$response = Requests::get($url . '/api/v1/' . $type . '/' . $id, $headers, $options);
if ($response->success) {
$metadata = json_decode($response->body, true);
$this->setResponse(200, null, $metadata);
return $metadata;
} else {
$this->setResponse(500, 'Error getting series information');
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Overseerr')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function overseerrTVDefault($type)
{
return $type == $this->config['overseerrTvDefault'];
}
}

View File

@@ -0,0 +1,231 @@
<?php
trait PiHoleHomepageItem
{
public function piholeSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Pi-hole',
'enabled' => true,
'image' => 'plugins/images/tabs/pihole.png',
'category' => 'Monitor',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepagePiholeEnabled'),
$this->settingsOption('auth', 'homepagePiholeAuth'),
],
'Connection' => [
$this->settingsOption('multiple-url', 'piholeURL', ['help' => 'Please make sure to use local IP address and port at the end of the URL. You can add multiple Pi-holes by comma separating the URLs.', 'placeholder' => 'http(s)://hostname:port/']),
$this->settingsOption('multiple-token', 'piholeToken'),
],
'Misc' => [
$this->settingsOption('toggle-title', 'piholeHeaderToggle'),
$this->settingsOption('switch', 'homepagePiholeCombine', ['label' => 'Combine stat cards', 'help' => 'This controls whether to combine the stats for multiple pihole instances into 1 card.']),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'pihole'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionPihole()
{
if (empty($this->config['piholeURL'])) {
$this->setAPIResponse('error', 'Pihole URL is not defined', 422);
return false;
}
$failed = false;
$errors = '';
$list = $this->csvHomepageUrlToken($this->config['piholeURL'], $this->config['piholeToken']);
foreach ($list as $key => $value) {
$response = $this->getAuth($value['url'], $value['token']);
$errors = $response["errors"];
}
if ($errors != '') {
$this->setAPIResponse('error', $errors, 500);
return false;
} else {
$this->setAPIResponse('success', null, 200);
return true;
}
}
public function piholeHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepagePiholeEnabled'
],
'auth' => [
'homepagePiholeAuth'
],
'not_empty' => [
'piholeURL'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderPihole()
{
if ($this->homepageItemPermissions($this->piholeHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Pihole...</h2></div>
<script>
// Pi-hole Stats
homepagePihole("' . $this->config['homepagePiholeRefresh'] . '");
// End Pi-hole Stats
</script>
</div>
';
}
}
public function getAuth($base_url, $token)
{
$sid = null;
if ($token === '' || $token === null) {
$errors .= $base_url . ': Missing API Token';
$this->setAPIResponse('error', $errors, 500);
return $errors;
}
try {
$sid = $this->doRequest($base_url, "createAuth", [], ['password' => $token]);
$this->cleanSessions($base_url, $sid);
} catch (Requests_Exception $e) {
$errors .= $ip . ': ' . $e->getMessage();
$this->setLoggerChannel('PiHole')->error($e);
};
return ["errors" => $errors, "sid" => $sid];
}
public function cleanSessions($base_url, $sid)
{
$sessions = $this->doRequest($base_url, "getAuths", ["sid" => $sid]);
foreach ($sessions as $session) {
// Skip if not right user agent, skip if current session
if ($session['user_agent'] != 'Organizr' || $session['current_session'] == '1') {
continue;
}
$this->doRequest($base_url,"deleteAuth", ["sid" => $sid], $session['id']);
}
}
public function endpoints($endpoint)
{
return [
"createAuth" => [
"type" => "post",
"urlHandler" => function() {
return "auth";
},
"responseHandler" => function($payload) {
return $payload['session']['sid'];
},
"payloadHandler" => function($data) {
return json_encode($data);
}
],
"getAuths" => [
"type" => "get",
"urlHandler" => function() {
return "auth/sessions";
},
"responseHandler" => function($payload) {
return $payload['sessions'];
}
],
"deleteAuth" => [
"type" => "delete",
"urlHandler" => function($id) {
return "auth/session/$id";
},
],
"get24HourStatsSummary" => [
"type" => "get",
"urlHandler" => function() {
$nowUnixTimestamp = time();
$oneDayAgoUnixTimestamp = $nowUnixTimestamp - (24 * 60 * 60);
return "stats/database/summary?from=$oneDayAgoUnixTimestamp&until=$nowUnixTimestamp";
}
],
"get24HourBlockedDomains" => [
"type" => "get",
"urlHandler" => function() {
$nowUnixTimestamp = time();
$oneDayAgoUnixTimestamp = $nowUnixTimestamp - (24 * 60 * 60);
return "stats/database/top_domains?from=$oneDayAgoUnixTimestamp&until=$nowUnixTimestamp&blocked=true&count=1000";
},
"responseHandler" => function($payload) {
return array_map(
function($item) {
return $item['domain'];
}, $payload['domains']
);
}
],
][$endpoint];
}
public function doRequest($baseUrl, $endpoint, $headers = [], $data = null)
{
$endpointDictionary = $this->endpoints($endpoint);
$urlHandler = $endpointDictionary['urlHandler'];
$payloadHandler = $endpointDictionary['payloadHandler'] ?? function() {
return null;
};
$requestType = $endpointDictionary['type'];
$responseHandler = $endpointDictionary['responseHandler'] ?? function($payload) {
return $payload;
};
$url = $this->qualifyURL("$baseUrl/api/{$urlHandler($data)}");
$headers = $headers + ["User-Agent" => 'Organizr'];
try {
$response = Requests::$requestType($url, $headers, $payloadHandler($data));
if ($response->success) {
$processedResponse = $responseHandler($this->testAndFormatString($response->body)["data"]);
}
} catch (Requests_Exception $e) {
$this->setResponse(500, $e->getMessage());
$this->setLoggerChannel('PiHole')->error($e);
};
return $processedResponse ?? [];
}
public function getPiholeHomepageStats()
{
if (!$this->homepageItemPermissions($this->piholeHomepagePermissions('main'), true)) {
return false;
}
$api = [];
$list = $this->csvHomepageUrlToken($this->config['piholeURL'], $this->config['piholeToken']);
foreach ($list as $key => $value) {
$base_url = $value['url'];
$sid = $this->getAuth($base_url, $value['token'])["sid"];
$stats = $this->doRequest($base_url, "get24HourStatsSummary", ["sid" => $sid]);
$stats["domains_being_blocked"] = $this->doRequest($base_url, "get24HourBlockedDomains", ["sid" => $sid]);
$api['data'][$base_url] = $stats;
}
$api['options']['combine'] = $this->config['homepagePiholeCombine'];
$api['options']['title'] = $this->config['piholeHeaderToggle'];
$api = $api ?? null;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
}

View File

@@ -0,0 +1,739 @@
<?php
trait PlexHomepageItem
{
public function plexSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Plex',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/plex.png',
'category' => 'Media Server',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$libraryList = [['name' => 'Refresh page to update List', 'value' => '', 'disabled' => true]];
if ($this->config['plexID'] !== '' && $this->config['plexToken'] !== '') {
$loop = $this->plexLibraryList('key');
if ($loop) {
$loop = $loop['libraries'];
foreach ($loop as $key => $value) {
$libraryList[] = ['name' => $key, 'value' => $value];
}
}
}
$homepageSettings = [
'docs' => $this->docs('features/homepage/plex-homepage-item'),
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepagePlexEnabled'),
$this->settingsOption('auth', 'homepagePlexAuth'),
],
'Connection' => [
$this->settingsOption('url', 'plexURL'),
$this->settingsOption('blank'),
$this->settingsOption('disable-cert-check', 'plexDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'plexUseCustomCertificate'),
$this->settingsOption('token', 'plexToken'),
$this->settingsOption('button', '', ['label' => 'Get Plex Token', 'icon' => 'fa fa-ticket', 'text' => 'Retrieve', 'attr' => 'onclick="PlexOAuth(oAuthSuccess,oAuthError, oAuthMaxRetry, null, null, \'#homepage-Plex-form [name=plexToken]\')"']),
$this->settingsOption('password-alt', 'plexID', ['label' => 'Plex Machine']),
$this->settingsOption('button', '', ['label' => 'Get Plex Machine', 'icon' => 'fa fa-id-badge', 'text' => 'Retrieve', 'attr' => 'onclick="showPlexMachineForm(\'#homepage-Plex-form [name=plexID]\')"']),
],
'Active Streams' => [
$this->settingsOption('enable', 'homepagePlexStreams'),
$this->settingsOption('auth', 'homepagePlexStreamsAuth'),
$this->settingsOption('switch', 'homepageShowStreamNames', ['label' => 'User Information', 'help' => 'Show user and IP information']),
$this->settingsOption('auth', 'homepageShowStreamNamesAuth'),
$this->settingsOption('switch', 'homepageShowStreamNamesWithoutIp', ['label' => 'User Information Without IP', 'help' => 'Only shows username and no IP information']),
$this->settingsOption('auth', 'homepageShowStreamNamesWithoutIpAuth'),
$this->settingsOption('refresh', 'homepageStreamRefresh'),
$this->settingsOption('plex-library-exclude', 'homepagePlexStreamsExclude', ['options' => $libraryList]),
],
'Recent Items' => [
$this->settingsOption('enable', 'homepagePlexRecent'),
$this->settingsOption('auth', 'homepagePlexRecentAuth'),
$this->settingsOption('plex-library-exclude', 'homepagePlexRecentExclude', ['options' => $libraryList]),
$this->settingsOption('limit', 'homepageRecentLimit'),
$this->settingsOption('refresh', 'homepageRecentRefresh'),
],
'Media Search' => [
$this->settingsOption('enable', 'mediaSearch'),
$this->settingsOption('auth', 'mediaSearchAuth'),
$this->settingsOption('plex-library-exclude', 'homepagePlexSearchExclude', ['options' => $libraryList]),
$this->settingsOption('media-search-server', 'mediaSearchType'),
],
'Playlists' => [
$this->settingsOption('enable', 'homepagePlexPlaylist'),
$this->settingsOption('auth', 'homepagePlexPlaylistAuth'),
],
'Misc Options' => [
$this->settingsOption('input', 'plexTabName', ['label' => 'Plex Tab Name', 'placeholder' => 'Only use if you have Plex in a reverse proxy']),
$this->settingsOption('input', 'plexTabURL', ['label' => 'Plex Tab WAN URL', 'placeholder' => 'http(s)://domain.com/plex']),
$this->settingsOption('image-cache-quality', 'cacheImageSize'),
$this->settingsOption('blank'),
$this->settingsOption('switch', 'homepageUseCustomStreamNames', ['label' => 'Use Tautulli custom names for users']),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'plex'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionPlex()
{
if (!empty($this->config['plexURL']) && !empty($this->config['plexToken'])) {
$url = $this->qualifyURL($this->config['plexURL']) . "/servers?X-Plex-Token=" . $this->config['plexToken'];
try {
$options = $this->requestOptions($url, null, $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
$response = Requests::get($url, [], $options);
libxml_use_internal_errors(true);
if ($response->success) {
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
} else {
$this->setAPIResponse('error', 'URL and/or Token not setup correctly', 422);
return false;
}
} catch (Requests_Exception $e) {
$this->setResponse(500, $e->getMessage());
return false;
}
} else {
$this->setAPIResponse('error', 'URL and/or Token not setup', 422);
return 'URL and/or Token not setup';
}
}
public function plexHomepagePermissions($key = null)
{
$permissions = [
'streams' => [
'enabled' => [
'homepagePlexEnabled',
'homepagePlexStreams'
],
'auth' => [
'homepagePlexAuth',
'homepagePlexStreamsAuth'
],
'not_empty' => [
'plexURL',
'plexToken',
'plexID'
]
],
'recent' => [
'enabled' => [
'homepagePlexEnabled',
'homepagePlexRecent'
],
'auth' => [
'homepagePlexAuth',
'homepagePlexRecentAuth'
],
'not_empty' => [
'plexURL',
'plexToken',
'plexID'
]
],
'playlists' => [
'enabled' => [
'homepagePlexEnabled',
'homepagePlexPlaylist'
],
'auth' => [
'homepagePlexAuth',
'homepagePlexPlaylistAuth'
],
'not_empty' => [
'plexURL',
'plexToken',
'plexID'
]
],
'metadata' => [
'enabled' => [
'homepagePlexEnabled'
],
'auth' => [
'homepagePlexAuth'
],
'not_empty' => [
'plexURL',
'plexToken',
'plexID'
]
],
'search' => [
'enabled' => [
'homepagePlexEnabled',
'mediaSearch'
],
'auth' => [
'homepagePlexAuth',
'mediaSearchAuth'
],
'not_empty' => [
'plexURL',
'plexToken',
'plexID'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderplexnowplaying()
{
if ($this->homepageItemPermissions($this->plexHomepagePermissions('streams'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Now Playing...</h2></div>
<script>
// Plex Stream
homepageStream("plex", "' . $this->config['homepageStreamRefresh'] . '");
// End Plex Stream
</script>
</div>
';
}
}
public function homepageOrderplexrecent()
{
if ($this->homepageItemPermissions($this->plexHomepagePermissions('recent'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Recent...</h2></div>
<script>
// Plex Recent
homepageRecent("plex", "' . $this->config['homepageRecentRefresh'] . '");
// End Plex Recent
</script>
</div>
';
}
}
public function homepageOrderplexplaylist()
{
if ($this->homepageItemPermissions($this->plexHomepagePermissions('playlists'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Playlists...</h2></div>
<script>
// Plex Playlist
homepagePlaylist("plex");
// End Plex Playlist
</script>
</div>
';
}
}
public function getPlexHomepageStreams()
{
if (!$this->homepageItemPermissions($this->plexHomepagePermissions('streams'), true)) {
return false;
}
if ($this->demo) {
return $this->demoData('plex/plex-streams.json');
}
$this->setTautulliFriendlyNames();
$ignore = array();
$exclude = explode(',', $this->config['homepagePlexStreamsExclude']);
$resolve = true;
$url = $this->qualifyURL($this->config['plexURL']);
$url = $url . "/status/sessions?X-Plex-Token=" . $this->config['plexToken'];
$options = $this->requestOptions($url, $this->config['homepageStreamRefresh'], $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
try {
$response = Requests::get($url, [], $options);
libxml_use_internal_errors(true);
if ($response->success) {
$items = array();
$plex = simplexml_load_string($response->body);
foreach ($plex as $child) {
if (!in_array($child['type'], $ignore) && !in_array($child['librarySectionID'], $exclude) && isset($child['librarySectionID'])) {
$items[] = $this->resolvePlexItem($child);
}
}
$api['content'] = ($resolve) ? $items : $plex;
$api['plexID'] = $this->config['plexID'];
$api['showNames'] = true;
$api['group'] = '1';
$this->setAPIResponse('success', null, 200, $api);
return $api;
} else {
$this->setAPIResponse('error', null, 401, []);
return [];
}
} catch (Exception $e) {
$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
return false;
}
}
public function getPlexHomepageRecent()
{
if (!$this->homepageItemPermissions($this->plexHomepagePermissions('recent'), true)) {
return false;
}
$ignore = array();
$exclude = explode(',', $this->config['homepagePlexRecentExclude']);
$resolve = true;
$url = $this->qualifyURL($this->config['plexURL']);
$urls['movie'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $this->config['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $this->config['homepageRecentLimit'] . "&type=1";
$urls['tv'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $this->config['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $this->config['homepageRecentLimit'] . "&type=2";
$urls['music'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $this->config['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $this->config['homepageRecentLimit'] . "&type=8";
try {
foreach ($urls as $k => $v) {
$options = $this->requestOptions($url, $this->config['homepageRecentRefresh'], $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
$response = Requests::get($v, [], $options);
libxml_use_internal_errors(true);
if ($response->success) {
$items = array();
$plex = simplexml_load_string($response->body);
foreach ($plex as $child) {
if (!in_array($child['type'], $ignore) && !in_array($child['librarySectionID'], $exclude) && isset($child['librarySectionID'])) {
$items[] = $this->resolvePlexItem($child);
}
}
if (isset($api)) {
$api['content'] = array_merge($api['content'], ($resolve) ? $items : $plex);
} else {
$api['content'] = ($resolve) ? $items : $plex;
}
}
}
if (isset($api['content'])) {
usort($api['content'], function ($a, $b) {
return $b['addedAt'] <=> $a['addedAt'];
});
}
$api['plexID'] = $this->config['plexID'];
$api['showNames'] = true;
$api['group'] = '1';
$this->setAPIResponse('success', null, 200, $api);
return $api;
} catch (Exception $e) {
$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
return false;
}
}
public function getPlexHomepagePlaylists()
{
if (!$this->homepageItemPermissions($this->plexHomepagePermissions('playlists'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['plexURL']);
$url = $url . "/playlists?X-Plex-Token=" . $this->config['plexToken'];
$options = $this->requestOptions($url, null, $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
try {
$response = Requests::get($url, [], $options);
libxml_use_internal_errors(true);
if ($response->success) {
$items = array();
$plex = simplexml_load_string($response->body);
foreach ($plex as $child) {
if ($child['playlistType'] == "video" && strpos(strtolower($child['title']), 'private') === false) {
$playlistTitleClean = preg_replace("/(\W)+/", "", (string)$child['title']);
$playlistURL = $this->qualifyURL($this->config['plexURL']);
$playlistURL = $playlistURL . $child['key'] . "?X-Plex-Token=" . $this->config['plexToken'];
$options = ($this->localURL($url)) ? array('verify' => false) : array();
$playlistResponse = Requests::get($playlistURL, array(), $options);
if ($playlistResponse->success) {
$playlistResponse = simplexml_load_string($playlistResponse->body);
$items[$playlistTitleClean]['title'] = (string)$child['title'];
foreach ($playlistResponse->Video as $playlistItem) {
$items[$playlistTitleClean][] = $this->resolvePlexItem($playlistItem);
}
}
}
}
$api['content'] = $items;
$api['plexID'] = $this->config['plexID'];
$api['showNames'] = true;
$api['group'] = '1';
$this->setAPIResponse('success', null, 200, $api);
return $api;
} else {
$this->setAPIResponse('error', 'Plex API error', 500);
return false;
}
} catch (Exception $e) {
$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
return false;
}
}
public function getPlexHomepageMetadata($array)
{
if (!$this->homepageItemPermissions($this->plexHomepagePermissions('metadata'), true)) {
return false;
}
if ($this->demo) {
return $this->demoData('plex/plex-metadata.json');
}
$key = $array['key'] ?? null;
if (!$key) {
$this->setAPIResponse('error', 'Plex Metadata key is not defined', 422);
return false;
}
$ignore = array();
$resolve = true;
$url = $this->qualifyURL($this->config['plexURL']);
$url = $url . "/library/metadata/" . $key . "?X-Plex-Token=" . $this->config['plexToken'];
$options = $this->requestOptions($url, null, $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
try {
$response = Requests::get($url, [], $options);
libxml_use_internal_errors(true);
if ($response->success) {
$items = array();
$plex = simplexml_load_string($response->body);
foreach ($plex as $child) {
if (!in_array($child['type'], $ignore) && isset($child['librarySectionID'])) {
$items[] = $this->resolvePlexItem($child);
}
}
$api['content'] = ($resolve) ? $items : $plex;
$api['plexID'] = $this->config['plexID'];
$api['showNames'] = true;
$api['group'] = '1';
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
} catch (Exception $e) {
$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
return false;
}
}
public function getPlexHomepageSearch($query)
{
if (!$this->homepageItemPermissions($this->plexHomepagePermissions('search'), true)) {
return false;
}
$query = $query ?? null;
if (!$query) {
$this->setAPIResponse('error', 'Plex Metadata key is not defined', 422);
return false;
}
$ignore = array('artist', 'episode');
$exclude = explode(',', $this->config['homepagePlexSearchExclude']);
$resolve = true;
$url = $this->qualifyURL($this->config['plexURL']);
$url = $url . "/search?query=" . rawurlencode($query) . "&X-Plex-Token=" . $this->config['plexToken'];
$options = $this->requestOptions($url, null, $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
try {
$response = Requests::get($url, [], $options);
libxml_use_internal_errors(true);
if ($response->success) {
$items = array();
$plex = simplexml_load_string($response->body);
foreach ($plex as $child) {
if (!in_array($child['type'], $ignore) && !in_array($child['librarySectionID'], $exclude) && isset($child['librarySectionID'])) {
$items[] = $this->resolvePlexItem($child);
}
}
$api['content'] = ($resolve) ? $items : $plex;
$api['plexID'] = $this->config['plexID'];
$api['showNames'] = true;
$api['group'] = '1';
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
} catch (Exception $e) {
$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
return false;
}
}
public function resolvePlexItem($item)
{
// Static Height & Width
$height = $this->getCacheImageSize('h');
$width = $this->getCacheImageSize('w');
$nowPlayingHeight = $this->getCacheImageSize('nph');
$nowPlayingWidth = $this->getCacheImageSize('npw');
// Cache Directories
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$cacheDirectoryWeb = 'data/cache/';
// Types
switch ($item['type']) {
case 'show':
$plexItem['type'] = 'tv';
$plexItem['title'] = (string)$item['title'];
$plexItem['secondaryTitle'] = (string)$item['year'];
$plexItem['summary'] = (string)$item['summary'];
$plexItem['ratingKey'] = (string)$item['ratingKey'];
$plexItem['thumb'] = (string)$item['thumb'];
$plexItem['key'] = (string)$item['ratingKey'] . "-list";
$plexItem['nowPlayingThumb'] = (string)$item['art'];
$plexItem['nowPlayingKey'] = (string)$item['ratingKey'] . "-np";
$plexItem['nowPlayingTitle'] = (string)$item['title'];
$plexItem['nowPlayingBottom'] = (string)$item['year'];
$plexItem['metadataKey'] = (string)$item['ratingKey'];
break;
case 'season':
$plexItem['type'] = 'tv';
$plexItem['title'] = (string)$item['parentTitle'];
$plexItem['secondaryTitle'] = (string)$item['title'];
$plexItem['summary'] = (string)$item['parentSummary'];
$plexItem['ratingKey'] = (string)$item['parentRatingKey'];
$plexItem['thumb'] = (string)$item['thumb'];
$plexItem['key'] = (string)$item['ratingKey'] . "-list";
$plexItem['nowPlayingThumb'] = (string)$item['art'];
$plexItem['nowPlayingKey'] = (string)$item['ratingKey'] . "-np";
$plexItem['metadataKey'] = (string)$item['parentRatingKey'];
break;
case 'episode':
$useImage = (isset($item['live']) ? 'plugins/images/homepage/livetv.png' : null);
$plexItem['type'] = 'tv';
$plexItem['title'] = (string)$item['grandparentTitle'];
$plexItem['secondaryTitle'] = (string)$item['parentTitle'] . ' - Episode ' . (string)$item['index'];
$plexItem['summary'] = (string)$item['title'];
$plexItem['ratingKey'] = (string)($item['parentRatingKey'] ?? $item['ratingKey']);
$plexItem['thumb'] = ($item['parentThumb'] ? (string)$item['parentThumb'] : (string)$item['grandparentThumb']);
$plexItem['key'] = (string)$item['ratingKey'] . "-list";
$plexItem['nowPlayingThumb'] = (string)$item['grandparentArt'];
$plexItem['nowPlayingKey'] = (string)$item['grandparentRatingKey'] . "-np";
$plexItem['nowPlayingTitle'] = (string)$item['grandparentTitle'] . ' - ' . (string)$item['title'];
$plexItem['nowPlayingBottom'] = 'S' . (string)$item['parentIndex'] . ' · E' . (string)$item['index'];
$plexItem['metadataKey'] = (string)($item['grandparentRatingKey'] ?? $item['parentRatingKey'] ?? $item['ratingKey']);
break;
case 'clip':
$useImage = (isset($item['live']) ? "plugins/images/homepage/livetv.png" : null);
$plexItem['type'] = 'clip';
$plexItem['title'] = (isset($item['live']) ? 'Live TV' : (string)$item['title']);
$plexItem['secondaryTitle'] = '';
$plexItem['summary'] = (string)$item['summary'];
$plexItem['ratingKey'] = (string)$item['parentRatingKey'];
$plexItem['thumb'] = (string)$item['thumb'];
$plexItem['key'] = (string)$item['ratingKey'] . "-list";
$plexItem['nowPlayingThumb'] = (string)$item['art'];
$plexItem['nowPlayingKey'] = isset($item['ratingKey']) ? (string)$item['ratingKey'] . "-np" : (isset($item['live']) ? "livetv.png" : ":)");
$plexItem['nowPlayingTitle'] = $plexItem['title'];
$plexItem['nowPlayingBottom'] = isset($item['extraType']) ? "Trailer" : (isset($item['live']) ? "Live TV" : ":)");
break;
case 'album':
case 'track':
$plexItem['type'] = 'music';
$plexItem['title'] = (string)$item['parentTitle'];
$plexItem['secondaryTitle'] = (string)$item['title'];
$plexItem['summary'] = (string)$item['title'];
$plexItem['ratingKey'] = (string)$item['parentRatingKey'];
$plexItem['thumb'] = (string)$item['thumb'];
$plexItem['key'] = (string)$item['ratingKey'] . "-list";
$plexItem['nowPlayingThumb'] = ($item['parentThumb']) ? (string)$item['parentThumb'] : (string)$item['art'];
$plexItem['nowPlayingKey'] = (string)$item['parentRatingKey'] . "-np";
$plexItem['nowPlayingTitle'] = (string)$item['grandparentTitle'] . ' - ' . (string)$item['title'];
$plexItem['nowPlayingBottom'] = (string)$item['parentTitle'];
$plexItem['metadataKey'] = isset($item['grandparentRatingKey']) ? (string)$item['grandparentRatingKey'] : (string)$item['parentRatingKey'];
break;
default:
$useImage = (isset($item['live']) ? 'plugins/images/homepage/livetv.png' : null);
$plexItem['type'] = 'movie';
$plexItem['title'] = (string)$item['title'];
$plexItem['secondaryTitle'] = (string)$item['year'];
$plexItem['summary'] = (string)$item['summary'];
$plexItem['ratingKey'] = (string)$item['ratingKey'];
$plexItem['thumb'] = (string)$item['thumb'];
$plexItem['key'] = (string)$item['ratingKey'] . "-list";
$plexItem['nowPlayingThumb'] = (string)$item['art'];
$plexItem['nowPlayingKey'] = (string)$item['ratingKey'] . "-np";
$plexItem['nowPlayingTitle'] = (string)$item['title'];
$plexItem['nowPlayingBottom'] = (string)$item['year'];
$plexItem['metadataKey'] = (string)$item['ratingKey'];
}
$plexItem['originalType'] = $item['type'];
$plexItem['uid'] = (string)$item['ratingKey'];
$plexItem['elapsed'] = isset($item['viewOffset']) && $item['viewOffset'] !== '0' ? (int)$item['viewOffset'] : null;
$plexItem['duration'] = isset($item['duration']) ? (int)$item['duration'] : (int)$item->Media['duration'];
$plexItem['addedAt'] = isset($item['addedAt']) ? (int)$item['addedAt'] : null;
$plexItem['watched'] = ($plexItem['elapsed'] && $plexItem['duration'] ? floor(($plexItem['elapsed'] / $plexItem['duration']) * 100) : 0);
$plexItem['transcoded'] = isset($item->TranscodeSession['progress']) ? floor((int)$item->TranscodeSession['progress'] - $plexItem['watched']) : '';
$plexItem['stream'] = isset($item->Media->Part->Stream['decision']) ? (string)$item->Media->Part->Stream['decision'] : '';
$plexItem['id'] = str_replace('"', '', (string)$item->Player['machineIdentifier']);
$plexItem['session'] = (string)$item->Session['id'];
$plexItem['bandwidth'] = (string)$item->Session['bandwidth'];
$plexItem['bandwidthType'] = (string)$item->Session['location'];
$plexItem['sessionType'] = isset($item->TranscodeSession['progress']) ? 'Transcoding' : 'Direct Playing';
$plexItem['state'] = (((string)$item->Player['state'] == "paused") ? "pause" : "play");
$plexItem['user'] = $this->formatPlexUserName($item);
$plexItem['userThumb'] = (($this->config['homepageShowStreamNames'] && $this->qualifyRequest($this->config['homepageShowStreamNamesAuth'])) || ($this->config['homepageShowStreamNamesWithoutIp'] && $this->qualifyRequest($this->config['homepageShowStreamNamesWithoutIpAuth']))) ? (string)$item->User['thumb'] : "";
$plexItem['userAddress'] = ($this->config['homepageShowStreamNames'] && $this->qualifyRequest($this->config['homepageShowStreamNamesAuth'])) ? (string)$item->Player['address'] : "x.x.x.x";
$plexItem['address'] = $this->config['plexTabURL'] ? $this->config['plexTabURL'] . "/web/index.html#!/server/" . $this->config['plexID'] . "/details?key=/library/metadata/" . $item['ratingKey'] : "https://app.plex.tv/web/app#!/server/" . $this->config['plexID'] . "/details?key=/library/metadata/" . $item['ratingKey'];
$plexItem['nowPlayingOriginalImage'] = 'api/v2/homepage/image?source=plex&img=' . $plexItem['nowPlayingThumb'] . '&height=' . $nowPlayingHeight . '&width=' . $nowPlayingWidth . '&key=' . $plexItem['nowPlayingKey'] . '$' . $this->randString();
$plexItem['originalImage'] = 'api/v2/homepage/image?source=plex&img=' . $plexItem['thumb'] . '&height=' . $height . '&width=' . $width . '&key=' . $plexItem['key'] . '$' . $this->randString();
$plexItem['openTab'] = $this->config['plexTabURL'] && $this->config['plexTabName'] ? true : false;
$plexItem['tabName'] = $this->config['plexTabName'] ? $this->config['plexTabName'] : '';
// Stream info
$plexItem['userStream'] = array(
'platform' => (string)$item->Player['platform'],
'product' => (string)$item->Player['product'],
'device' => (string)$item->Player['device'],
'stream' => isset($item->Media) ? (string)$item->Media->Part['decision'] . ($item->TranscodeSession['throttled'] == '1' ? ' (Throttled)' : '') : '',
'videoResolution' => (string)$item->Media['videoResolution'],
'throttled' => ($item->TranscodeSession['throttled'] == 1) ? true : false,
'sourceVideoCodec' => (string)$item->TranscodeSession['sourceVideoCodec'],
'videoCodec' => (string)$item->TranscodeSession['videoCodec'],
'audioCodec' => (string)$item->TranscodeSession['audioCodec'],
'sourceAudioCodec' => (string)$item->TranscodeSession['sourceAudioCodec'],
'videoDecision' => $this->streamType((string)$item->TranscodeSession['videoDecision']),
'audioDecision' => $this->streamType((string)$item->TranscodeSession['audioDecision']),
'container' => (string)$item->TranscodeSession['container'],
'audioChannels' => (string)$item->TranscodeSession['audioChannels']
);
// Genre catch all
if ($item->Genre) {
$genres = array();
foreach ($item->Genre as $key => $value) {
$genres[] = (string)$value['tag'];
}
}
// Actor catch all
if ($item->Role) {
$actors = array();
foreach ($item->Role as $key => $value) {
if ($value['thumb']) {
$actors[] = array(
'name' => (string)$value['tag'],
'role' => (string)$value['role'],
'thumb' => (string)$value['thumb']
);
}
}
}
// Metadata information
$plexItem['metadata'] = array(
'guid' => (string)$item['guid'],
'summary' => (string)$item['summary'],
'rating' => (string)$item['rating'],
'duration' => (string)$item['duration'],
'originallyAvailableAt' => (string)$item['originallyAvailableAt'],
'year' => (string)$item['year'],
'studio' => (string)$item['studio'],
'tagline' => (string)$item['tagline'],
'genres' => ($item->Genre) ? $genres : '',
'actors' => ($item->Role) ? $actors : ''
);
if (file_exists($cacheDirectory . $plexItem['nowPlayingKey'] . '.jpg')) {
$plexItem['nowPlayingImageURL'] = $cacheDirectoryWeb . $plexItem['nowPlayingKey'] . '.jpg';
}
if (file_exists($cacheDirectory . $plexItem['key'] . '.jpg')) {
$plexItem['imageURL'] = $cacheDirectoryWeb . $plexItem['key'] . '.jpg';
}
if (file_exists($cacheDirectory . $plexItem['nowPlayingKey'] . '.jpg') && (time() - 604800) > filemtime($cacheDirectory . $plexItem['nowPlayingKey'] . '.jpg') || !file_exists($cacheDirectory . $plexItem['nowPlayingKey'] . '.jpg')) {
$plexItem['nowPlayingImageURL'] = 'api/v2/homepage/image?source=plex&img=' . $plexItem['nowPlayingThumb'] . '&height=' . $nowPlayingHeight . '&width=' . $nowPlayingWidth . '&key=' . $plexItem['nowPlayingKey'] . '';
}
if (file_exists($cacheDirectory . $plexItem['key'] . '.jpg') && (time() - 604800) > filemtime($cacheDirectory . $plexItem['key'] . '.jpg') || !file_exists($cacheDirectory . $plexItem['key'] . '.jpg')) {
$plexItem['imageURL'] = 'api/v2/homepage/image?source=plex&img=' . $plexItem['thumb'] . '&height=' . $height . '&width=' . $width . '&key=' . $plexItem['key'] . '';
}
if (!$plexItem['nowPlayingThumb']) {
$plexItem['nowPlayingOriginalImage'] = $plexItem['nowPlayingImageURL'] = "plugins/images/homepage/no-np.png";
$plexItem['nowPlayingKey'] = "no-np";
}
if (!$plexItem['thumb'] || $plexItem['addedAt'] >= (time() - 300)) {
$plexItem['originalImage'] = $plexItem['imageURL'] = "plugins/images/homepage/no-list.png";
$plexItem['key'] = "no-list";
}
if (isset($useImage)) {
$plexItem['useImage'] = $useImage;
}
return $plexItem;
}
public function getTautulliFriendlyNames($bypass = null)
{
$names = [];
if (!$this->qualifyRequest(1) && !$bypass) {
return false;
}
$url = $this->qualifyURL($this->config['tautulliURL']);
$url .= '/api/v2?apikey=' . $this->config['tautulliApikey'];
$url .= '&cmd=get_users';
$options = $this->requestOptions($url, null, $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate']);
try {
$response = Requests::get($url, [], $options);
if ($response->success) {
$response = json_decode($response->body, true);
foreach ($response['response']['data'] as $user) {
if ($user['user_id'] != 0) {
$names[$user['username']] = $user['friendly_name'];
}
}
}
} catch (Exception $e) {
$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
}
$this->setAPIResponse('success', null, 200, $names);
return $names;
}
public function setTautulliFriendlyNames()
{
if ($this->config['tautulliURL'] && $this->config['tautulliApikey'] && $this->config['homepageUseCustomStreamNames']) {
$names = $this->getTautulliFriendlyNames(true);
$names = json_encode($names);
if ($names !== $this->config['homepageCustomStreamNames']) {
$this->updateConfig(array('homepageCustomStreamNames' => $names));
$this->config['homepageCustomStreamNames'] = $names;
$this->setLoggerChannel('Tautulli');
$this->logger->debug('Updating Tautulli custom names config item', $names);
}
}
}
private function formatPlexUserName($item)
{
$name = (($this->config['homepageShowStreamNames'] && $this->qualifyRequest($this->config['homepageShowStreamNamesAuth'])) || ($this->config['homepageShowStreamNamesWithoutIp'] && $this->qualifyRequest($this->config['homepageShowStreamNamesWithoutIpAuth']))) ? (string)$item->User['title'] : "";
try {
if ($this->config['homepageUseCustomStreamNames']) {
$customNames = json_decode($this->config['homepageCustomStreamNames'], true);
if (array_key_exists($name, $customNames)) {
$name = $customNames[$name];
}
}
} catch (Exception $e) {
// don't do anythig if it goes wrong, like if the JSON is badly formatted
}
return $name;
}
public function plexLibraryList($value = 'id')
{
if (!empty($this->config['plexToken']) && !empty($this->config['plexID'])) {
$url = 'https://plex.tv/api/servers/' . $this->config['plexID'];
try {
$headers = array(
"Accept" => "application/json",
"X-Plex-Token" => $this->config['plexToken']
);
$response = Requests::get($url, $headers, array());
libxml_use_internal_errors(true);
if ($response->success) {
$libraryList = array();
$plex = simplexml_load_string($response->body);
foreach ($plex->Server->Section as $child) {
$libraryList['libraries'][(string)$child['title']] = (string)$child[$value];
}
$libraryList = array_change_key_case($libraryList, CASE_LOWER);
return $libraryList;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Plex')->error($e);
return false;
};
}
return false;
}
}

View File

@@ -0,0 +1,117 @@
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
trait PromPageHomepageItem
{
private static Client $kumaClient;
public function promPageSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'PromPage',
'enabled' => true,
'image' => 'plugins/images/tabs/prompage.png',
'category' => 'Monitor',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('html', null, ['override' => 6, 'label' => 'Info', 'html' => '<p>This homepage item requires <a href="https://github.com/henrywhitaker3/prompage" target="_blank" rel="noreferrer noopener">PromPage <i class="fa fa-external-link" aria-hidden="true"></i></a> to be running.</p>']),
$this->settingsOption('enable', 'homepagePromPageEnabled'),
],
'Connection' => [
$this->settingsOption('url', 'promPageURL', ['help' => 'URL for Uptime Kuma e.g. http://kuma:3001 (no trailing slash)', 'placeholder' => 'http://prompage:3000']),
],
'Options' => [
$this->settingsOption('refresh', 'homepagePromPageRefresh'),
$this->settingsOption('title', 'homepagePromPageHeader'),
$this->settingsOption('toggle-title', 'homepagePromPageHeaderToggle'),
$this->settingsOption('switch', 'homepagePromPageCompact', ['label' => 'Compact view', 'help' => 'Toggles the compact view of this homepage module']),
$this->settingsOption('switch', 'homepagePromPageShowUptime', ['label' => 'Show monitor uptime']),
],
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function promPageHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepagePromPageEnabled'
],
'not_empty' => [
'promPageURL',
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderPromPage()
{
if ($this->homepageItemPermissions($this->promPageHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Status Page...</h2></div>
<script>
// PromPage
homepagePromPage("' . $this->config['homepagePromPageRefresh'] . '");
// End PromPage
</script>
</div>
';
}
}
public function getpromPageHomepageData()
{
if (!$this->homepageItemPermissions($this->promPageHomepagePermissions('main'), true)) {
return false;
}
$api = [];
$url = $this->qualifyURL($this->config['promPageURL']);
try {
$services = json_decode($this->getPromPageClient($url, $this->config['promPageToken'])
->get('/api/services')
->getBody()
->getContents())->services;
$api = [
'data' => $services,
'options' => [
'title' => $this->config['homepagePromPageHeader'],
'titleToggle' => $this->config['homepagePromPageHeaderToggle'],
'compact' => $this->config['homepagePromPageCompact'],
'showUptime' => $this->config['homepagePromPageShowUptime'],
]
];
} catch (GuzzleException $e) {
$this->setLoggerChannel('promPage')->error($e);
$this->setAPIResponse('error', $e->getMessage(), 401);
return false;
};
$api = isset($api) ? $api : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
private function getPromPageClient(string $url): Client
{
if (!isset(static::$kumaClient)) {
static::$kumaClient = new Client([
'base_uri' => $url,
]);
}
return static::$kumaClient;
}
}

View File

@@ -0,0 +1,185 @@
<?php
trait ProwlarrHomepageItem
{
public function prowlarrSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Prowlarr',
'enabled' => true,
'image' => 'plugins/images/tabs/prowlarr.png',
'category' => 'Utility',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageProwlarrEnabled'),
$this->settingsOption('auth', 'homepageProwlarrAuth'),
],
'Connection' => [
$this->settingsOption('url', 'prowlarrURL'),
$this->settingsOption('token', 'prowlarrToken'),
$this->settingsOption('disable-cert-check', 'prowlarrDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'prowlarrUseCustomCertificate'),
],
'Options' => [
$this->settingsOption('switch', 'homepageProwlarrBackholeDownload', ['label' => 'Prefer black hole download', 'help' => 'Prefer black hole download link instead of direct/magnet download']),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'prowlarr'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function prowlarrHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageProwlarrEnabled'
],
'auth' => [
'homepageProwlarrAuth'
],
'not_empty' => [
'prowlarrURL',
'prowlarrToken'
]
],
'test' => [
'auth' => [
'homepageProwlarrAuth'
],
'not_empty' => [
'prowlarrURL',
'prowlarrToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderProwlarr()
{
if ($this->homepageItemPermissions($this->prowlarrHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Prowlarr...</h2></div>
<script>
// Prowlarr
homepageProwlarr();
// End Prowlarr
</script>
</div>
';
}
}
public function testConnectionProwlarr()
{
if (!$this->homepageItemPermissions($this->prowlarrHomepagePermissions('test'), true)) {
return false;
}
$apiURL = $this->qualifyURL($this->config['prowlarrURL']);
$endpoint = $apiURL . '/api/v1/search?apikey=' . $this->config['prowlarrToken'] . '&query=this-is-just-a-test-for-organizr';
try {
$headers = [];
$options = $this->requestOptions($apiURL, 120, $this->config['prowlarrDisableCertCheck'], $this->config['prowlarrUseCustomCertificate']);
$response = Requests::get($endpoint, $headers, $options);
if ($response->success) {
$apiData = json_decode($response->body, true);
$api['content'] = $apiData;
unset($apiData);
} else {
$this->setResponse(403, 'Error connecting to Prowlarr');
return false;
}
} catch (Requests_Exception $e) {
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = $api['content'] ?? false;
$this->setResponse(200, null, $api);
return $api;
}
public function searchProwlarrIndexers($query = null)
{
if (!$this->homepageItemPermissions($this->prowlarrHomepagePermissions('main'), true)) {
return false;
}
if (!$query) {
$this->setAPIResponse('error', 'Query was not supplied', 422);
return false;
}
$apiURL = $this->qualifyURL($this->config['prowlarrURL']);
$endpoint = $apiURL . '/api/v1/search?apikey=' . $this->config['prowlarrToken'] . '&query=' . urlencode($query);
try {
$headers = [];
$options = $this->requestOptions($apiURL, 120, $this->config['prowlarrDisableCertCheck'], $this->config['prowlarrUseCustomCertificate']);
$response = Requests::get($endpoint, $headers, $options);
if ($response->success) {
$apiData = json_decode($response->body, true);
$api['content'] = $apiData;
unset($apiData);
}
} catch (Requests_Exception $e) {
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = isset($api['content']) ? $api['content'] : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
public function performProwlarrBackHoleDownload($guid = null, $indexerId = null)
{
if (!$this->homepageItemPermissions($this->prowlarrHomepagePermissions('main'), true)) {
return false;
}
if (!$guid) {
$this->setAPIResponse('error', 'guid was not supplied', 422);
return false;
}
if (!$indexerId) {
$this->setAPIResponse('error', 'indexerId was not supplied', 422);
return false;
}
$apiURL = $this->qualifyURL($this->config['prowlarrURL']);
$endpoint = $apiURL . '/api/v1/search?apikey=' . $this->config['prowlarrToken'];
try {
$headers = [];
$data = ['guid'=>$guid,'indexerId'=>$indexerId];
$options = $this->requestOptions($apiURL, 120, $this->config['prowlarrDisableCertCheck'], $this->config['prowlarrUseCustomCertificate']);
$ch = curl_init($endpoint);
$payload = json_encode($data);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type:application/json'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
if ($response) {
$api['content'] = $response;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Prowlarr')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = isset($api['content']) ? $api['content'] : false;
if ($api['content']) {
$this->setAPIResponse('success', null, 200, $api);
} else {
$this->setAPIResponse('error', 'Unknown error', 400, $api);
}
return $api;
}
}

View File

@@ -0,0 +1,202 @@
<?php
trait QBitTorrentHomepageItem
{
public function qBittorrentSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'qBittorrent',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/qBittorrent.png',
'category' => 'Downloader',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageqBittorrentEnabled'),
$this->settingsOption('auth', 'homepageqBittorrentAuth'),
],
'Connection' => [
$this->settingsOption('url', 'qBittorrentURL'),
$this->settingsOption('select', 'qBittorrentApiVersion', ['label' => 'API Version', 'options' => $this->qBittorrentApiOptions()]),
$this->settingsOption('username', 'qBittorrentUsername'),
$this->settingsOption('password', 'qBittorrentPassword'),
$this->settingsOption('disable-cert-check', 'qBittorrentDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'qBittorrentUseCustomCertificate'),
],
'API SOCKS' => [
$this->settingsOption('socks', 'qbittorrent'),
$this->settingsOption('blank'),
$this->settingsOption('enable', 'qBittorrentSocksEnabled'),
$this->settingsOption('auth', 'qBittorrentSocksAuth'),
],
'Misc Options' => [
$this->settingsOption('hide-seeding', 'qBittorrentHideSeeding'),
$this->settingsOption('hide-completed', 'qBittorrentHideCompleted'),
$this->settingsOption('select', 'qBittorrentSortOrder', ['label' => 'Order', 'options' => $this->qBittorrentSortOptions()]),
$this->settingsOption('switch', 'qBittorrentReverseSorting', ['label' => 'Reverse Sorting']),
$this->settingsOption('refresh', 'qBittorrentRefresh'),
$this->settingsOption('combine', 'qBittorrentCombine'),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'qbittorrent'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionQBittorrent()
{
if (empty($this->config['qBittorrentURL'])) {
$this->setAPIResponse('error', 'qBittorrent URL is not defined', 422);
return false;
}
$digest = $this->qualifyURL($this->config['qBittorrentURL'], true);
$data = array('username' => $this->config['qBittorrentUsername'], 'password' => $this->decrypt($this->config['qBittorrentPassword']));
$apiVersionLogin = ($this->config['qBittorrentApiVersion'] == '1') ? '/login' : '/api/v2/auth/login';
$apiVersionQuery = ($this->config['qBittorrentApiVersion'] == '1') ? '/query/torrents?sort=' : '/api/v2/torrents/info?sort=';
$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $apiVersionLogin;
try {
$options = $this->requestOptions($this->config['qBittorrentURL'], null, $this->config['qBittorrentDisableCertCheck'], $this->config['qBittorrentUseCustomCertificate']);
$response = Requests::post($url, [], $data, $options);
$reflection = new ReflectionClass($response->cookies);
$cookie = $reflection->getProperty("cookies");
$cookie->setAccessible(true);
$cookie = $cookie->getValue($response->cookies);
if ($cookie) {
$headers = array(
'Cookie' => 'SID=' . $cookie['SID']->value
);
$reverse = $this->config['qBittorrentReverseSorting'] ? 'true' : 'false';
$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $apiVersionQuery . $this->config['qBittorrentSortOrder'] . '&reverse=' . $reverse;
$response = Requests::get($url, $headers, $options);
if ($response) {
$torrents = json_decode($response->body, true);
if (is_array($torrents)) {
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
} else {
$this->setAPIResponse('error', 'qBittorrent Error Occurred - Check URL or Credentials', 500);
return true;
}
} else {
$this->setAPIResponse('error', 'qBittorrent Connection Error Occurred - Check URL or Credentials', 500);
return true;
}
} else {
$this->setLoggerChannel('qBittorrent')->warning('Could not get session ID');
$this->setAPIResponse('error', 'qBittorrent Connect Function - Error: Could not get session ID', 409);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('qBittorrent')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function qBittorrentHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageqBittorrentEnabled'
],
'auth' => [
'homepageqBittorrentAuth'
],
'not_empty' => [
'qBittorrentURL'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderqBittorrent()
{
if ($this->homepageItemPermissions($this->qBittorrentHomepagePermissions('main'))) {
$loadingBox = ($this->config['qBittorrentCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
$builder = ($this->config['qBittorrentCombine']) ? 'buildDownloaderCombined(\'qBittorrent\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("qBittorrent"));';
return '
<div id="' . __FUNCTION__ . '">
' . $loadingBox . '
<script>
// homepageOrderqBittorrent
' . $builder . '
homepageDownloader("qBittorrent", "' . $this->config['qBittorrentRefresh'] . '");
// End homepageOrderqBittorrent
</script>
</div>
';
}
}
public function getQBittorrentHomepageQueue()
{
if (!$this->homepageItemPermissions($this->qBittorrentHomepagePermissions('main'), true)) {
return false;
}
$digest = $this->qualifyURL($this->config['qBittorrentURL'], true);
$data = array('username' => $this->config['qBittorrentUsername'], 'password' => $this->decrypt($this->config['qBittorrentPassword']));
$apiVersionLogin = ($this->config['qBittorrentApiVersion'] == '1') ? '/login' : '/api/v2/auth/login';
$apiVersionQuery = ($this->config['qBittorrentApiVersion'] == '1') ? '/query/torrents?sort=' : '/api/v2/torrents/info?sort=';
$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $apiVersionLogin;
try {
$options = $this->requestOptions($this->config['qBittorrentURL'], $this->config['qBittorrentRefresh'], $this->config['qBittorrentDisableCertCheck'], $this->config['qBittorrentUseCustomCertificate']);
$response = Requests::post($url, [], $data, $options);
$reflection = new ReflectionClass($response->cookies);
$cookie = $reflection->getProperty("cookies");
$cookie->setAccessible(true);
$cookie = $cookie->getValue($response->cookies);
if ($cookie) {
$headers = array(
'Cookie' => 'SID=' . $cookie['SID']->value
);
$reverse = $this->config['qBittorrentReverseSorting'] ? 'true' : 'false';
$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $apiVersionQuery . $this->config['qBittorrentSortOrder'] . '&reverse=' . $reverse;
$response = Requests::get($url, $headers, $options);
if ($response) {
$torrentList = json_decode($response->body, true);
if ($this->config['qBittorrentHideSeeding'] || $this->config['qBittorrentHideCompleted']) {
$filter = array();
$torrents = array();
if ($this->config['qBittorrentHideSeeding']) {
array_push($filter, 'uploading', 'stalledUP', 'queuedUP');
}
if ($this->config['qBittorrentHideCompleted']) {
array_push($filter, 'pausedUP');
}
foreach ($torrentList as $key => $value) {
if (!in_array($value['state'], $filter)) {
$torrents[] = $value;
}
}
} else {
$torrents = json_decode($response->body, true);
}
$api['content']['queueItems'] = $torrents;
$api['content']['historyItems'] = false;
$api['content'] = $api['content'] ?? false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
} else {
$this->setLoggerChannel('qBittorrent')->warning('Could not get session ID');
$this->setAPIResponse('error', 'qBittorrent Connect Function - Error: Could not get session ID', 409);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('qBittorrent')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,382 @@
<?php
trait RadarrHomepageItem
{
public function radarrSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Radarr',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/radarr.png',
'category' => 'PVR',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageRadarrEnabled'),
$this->settingsOption('auth', 'homepageRadarrAuth'),
],
'Connection' => [
$this->settingsOption('multiple-url', 'radarrURL'),
$this->settingsOption('multiple-token', 'radarrToken'),
$this->settingsOption('disable-cert-check', 'radarrDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'radarrUseCustomCertificate'),
],
'API SOCKS' => [
$this->settingsOption('socks', 'radarr'),
$this->settingsOption('blank'),
$this->settingsOption('enable', 'radarrSocksEnabled'),
$this->settingsOption('auth', 'radarrSocksAuth'),
],
'Queue' => [
$this->settingsOption('enable', 'homepageRadarrQueueEnabled'),
$this->settingsOption('auth', 'homepageRadarrQueueAuth'),
$this->settingsOption('combine', 'homepageRadarrQueueCombine'),
$this->settingsOption('refresh', 'homepageRadarrQueueRefresh'),
],
'Calendar' => [
$this->settingsOption('calendar-start', 'calendarStart'),
$this->settingsOption('calendar-end', 'calendarEnd'),
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
$this->settingsOption('calendar-default-view', 'calendarDefault'),
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
$this->settingsOption('calendar-locale', 'calendarLocale'),
$this->settingsOption('calendar-limit', 'calendarLimit'),
$this->settingsOption('refresh', 'calendarRefresh'),
$this->settingsOption('switch', 'radarrUnmonitored', ['label' => 'Show Unmonitored']),
$this->settingsOption('switch', 'radarrPhysicalRelease', ['label' => 'Show Physical Releases']),
$this->settingsOption('switch', 'radarrDigitalRelease', ['label' => 'Show Digital Releases']),
$this->settingsOption('switch', 'radarrCinemaRelease', ['label' => 'Show Cinema Releases']),
$this->settingsOption('blank', '', ['type' => 'html', 'html' => '<hr />']),
$this->settingsOption('blank', '', ['type' => 'html', 'html' => '<hr />']),
$this->settingsOption('enable', 'radarrIcon', ['label' => 'Show Radarr Icon']),
$this->settingsOption('calendar-link-url', 'radarrCalendarLink'),
$this->settingsOption('blank'),
$this->settingsOption('calendar-frame-target', 'radarrFrameTarget')
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'radarr'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionRadarr()
{
if (empty($this->config['radarrURL'])) {
$this->setAPIResponse('error', 'Radarr URL is not defined', 422);
return false;
}
if (empty($this->config['radarrToken'])) {
$this->setAPIResponse('error', 'Radarr Token is not defined', 422);
return false;
}
$failed = false;
$errors = '';
$list = $this->csvHomepageUrlToken($this->config['radarrURL'], $this->config['radarrToken']);
foreach ($list as $key => $value) {
try {
$options = $this->requestOptions($value['url'], null, $this->config['radarrDisableCertCheck'], $this->config['radarrUseCustomCertificate']);
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'radarr', null, null, $options);
$results = $downloader->getRootFolder();
$downloadList = json_decode($results, true);
if (is_array($downloadList) || is_object($downloadList)) {
$queue = (array_key_exists('error', $downloadList)) ? $downloadList['error']['msg'] : $downloadList;
if (!is_array($queue)) {
$ip = $value['url'];
$errors .= $ip . ': ' . $queue;
$failed = true;
}
} else {
$ip = $value['url'];
$errors .= $ip . ': Response was not JSON';
$failed = true;
}
} catch (Exception $e) {
$failed = true;
$ip = $value['url'];
$errors .= $ip . ': ' . $e->getMessage();
$this->setLoggerChannel('Radarr')->error($e);
}
}
if ($failed) {
$this->setAPIResponse('error', $errors, 500);
return false;
} else {
$this->setAPIResponse('success', null, 200);
return true;
}
}
public function radarrHomepagePermissions($key = null)
{
$permissions = [
'calendar' => [
'enabled' => [
'homepageRadarrEnabled'
],
'auth' => [
'homepageRadarrAuth'
],
'not_empty' => [
'radarrURL',
'radarrToken'
]
],
'queue' => [
'enabled' => [
'homepageRadarrEnabled',
'homepageRadarrQueueEnabled'
],
'auth' => [
'homepageRadarrAuth',
'homepageRadarrQueueAuth'
],
'not_empty' => [
'radarrURL',
'radarrToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderRadarrQueue()
{
if ($this->homepageItemPermissions($this->radarrHomepagePermissions('queue'))) {
$loadingBox = ($this->config['homepageRadarrQueueCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
$builder = ($this->config['homepageRadarrQueueCombine']) ? 'buildDownloaderCombined(\'radarr\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("radarr"));';
return '
<div id="' . __FUNCTION__ . '">
' . $loadingBox . '
<script>
// homepageOrderRadarrQueue
' . $builder . '
homepageDownloader("radarr", "' . $this->config['homepageRadarrQueueRefresh'] . '");
// End homepageOrderRadarrQueue
</script>
</div>
';
}
}
public function getRadarrQueue()
{
if (!$this->homepageItemPermissions($this->radarrHomepagePermissions('queue'), true)) {
return false;
}
$queueItems = array();
$list = $this->csvHomepageUrlToken($this->config['radarrURL'], $this->config['radarrToken']);
foreach ($list as $key => $value) {
try {
$options = $this->requestOptions($value['url'], $this->config['homepageRadarrQueueRefresh'], $this->config['radarrDisableCertCheck'], $this->config['radarrUseCustomCertificate']);
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'radarr', null, null, $options);
$results = $downloader->getQueue();
$downloadList = json_decode($results, true);
if (is_array($downloadList) || is_object($downloadList)) {
$queue = (array_key_exists('error', $downloadList)) ? [] : $downloadList;
$queue = $queue['records'] ?? $queue;
} else {
$queue = [];
}
if (!empty($queue)) {
$queueItems = array_merge($queueItems, $queue);
}
} catch (Exception $e) {
$this->logger->error($e);
}
}
$api['content']['queueItems'] = $queueItems;
$api['content']['historyItems'] = false;
$api['content'] = $api['content'] ?? false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
public function getRadarrCalendar($startDate = null, $endDate = null)
{
$startDate = ($startDate) ?? $_GET['start'] ?? date('Y-m-d', strtotime('-' . $this->config['calendarStart'] . ' days'));
$endDate = ($endDate) ?? $_GET['end'] ?? date('Y-m-d', strtotime('+' . $this->config['calendarEnd'] . ' days'));
if (!$this->homepageItemPermissions($this->radarrHomepagePermissions('calendar'), true)) {
return false;
}
if ($this->demo) {
return $this->demoData('radarr/calendar.json');
}
$calendarItems = array();
$list = $this->csvHomepageUrlToken($this->config['radarrURL'], $this->config['radarrToken']);
foreach ($list as $key => $value) {
try {
$options = $this->requestOptions($value['url'], $this->config['homepageRadarrQueueRefresh'], $this->config['radarrDisableCertCheck'], $this->config['radarrUseCustomCertificate']);
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'radarr', null, null, $options);
$results = $downloader->getCalendar($startDate, $endDate, $this->config['radarrUnmonitored']);
$result = json_decode($results, true);
if (is_array($result) || is_object($result)) {
$calendar = (array_key_exists('error', $result)) ? '' : $this->formatRadarrCalendar($results, $key, $value['url']);
} else {
$calendar = '';
}
} catch (Exception $e) {
$this->setLoggerChannel('Radarr')->error($e);
}
if (!empty($calendar)) {
$calendarItems = array_merge($calendarItems, $calendar);
}
}
$this->setAPIResponse('success', null, 200, $calendarItems);
return $calendarItems;
}
public function formatRadarrCalendar($array, $number, $url)
{
$url = rtrim($url, '/'); //remove trailing slash
$url = $url . '/api';
$array = json_decode($array, true);
$gotCalendar = array();
$i = 0;
foreach ($array as $child) {
for ($j = 0; $j < 3; $j++) {
$type = [];
if ($j == 0 && $this->config['radarrPhysicalRelease'] && isset($child['physicalRelease'])) {
$releaseDate = $child['physicalRelease'];
array_push($type, "physical");
if (isset($child['digitalRelease']) && $child['physicalRelease'] == $child['digitalRelease']) {
array_push($type, "digital");
$j++;
}
if (isset($child['inCinemas']) && $child['physicalRelease'] == $child['inCinemas']) {
array_push($type, "cinema");
$j += 2;
}
} elseif ($j == 1 && $this->config['radarrDigitalRelease'] && isset($child['digitalRelease'])) {
$releaseDate = $child['digitalRelease'];
array_push($type, "digital");
if (isset($child['inCinemas']) && $child['digitalRelease'] == $child['inCinemas']) {
array_push($type, "cinema");
$j++;
}
} elseif ($j == 2 && $this->config['radarrCinemaRelease'] && isset($child['inCinemas'])) {
$releaseDate = $child['inCinemas'];
array_push($type, "cinema");
} else {
continue;
}
$i++;
$movieName = $child['title'];
$movieID = $child['tmdbId'];
if (!isset($movieID)) {
$movieID = "";
}
$releaseDate = strtotime($releaseDate);
$releaseDate = date("Y-m-d", $releaseDate);
if (new DateTime() < new DateTime($releaseDate)) {
$notReleased = "true";
} else {
$notReleased = "false";
}
$downloaded = $child['hasFile'];
if ($downloaded == "0" && $notReleased == "true") {
$downloaded = "text-info";
} elseif ($downloaded == "1") {
$downloaded = "text-success";
} else {
$downloaded = "text-danger";
}
$banner = "/plugins/images/homepage/no-np.png";
foreach ($child['images'] as $image) {
if ($image['coverType'] == "banner" || $image['coverType'] == "fanart") {
if ($image['coverType'] == 'fanart' && (isset($image['remoteUrl']) && $image['remoteUrl'] !== '')) {
$banner = $image['remoteUrl'];
}elseif (strpos($image['url'], '://') === false) {
$imageUrl = $image['url'];
$urlParts = explode("/", $url);
$imageParts = explode("/", $image['url']);
if ($imageParts[1] == end($urlParts)) {
unset($imageParts[1]);
$imageUrl = implode("/", $imageParts);
}
$banner = $url . $imageUrl . '?apikey=' . $this->config['radarrToken'];
} else {
$banner = $image['url'];
}
}
}
if ($banner !== "/plugins/images/homepage/no-np.png" && (strpos($banner, 'apikey') !== false)) {
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$imageURL = $banner;
$cacheFile = $cacheDirectory . $movieID . '.jpg';
$banner = 'data/cache/' . $movieID . '.jpg';
if (!file_exists($cacheFile)) {
$this->cacheImage($imageURL, $movieID);
unset($imageURL);
unset($cacheFile);
}
}
$alternativeTitles = "";
if (!empty($child['alternativeTitles'])) {
foreach ($child['alternativeTitles'] as $alternative) {
$alternativeTitles .= $alternative['title'] . ', ';
}
} elseif (!empty($child['alternateTitles'])) { //v3 API
foreach ($child['alternateTitles'] as $alternative) {
$alternativeTitles .= $alternative['title'] . ', ';
}
}
$alternativeTitles = empty($alternativeTitles) ? "" : substr($alternativeTitles, 0, -2);
$href = $this->config['radarrCalendarLink'] ?? '';
if (empty($href) && !empty($this->config['radarrURL'])) {
$href_arr = explode(',', $this->config['radarrURL']);
$href = reset($href_arr);
}
if (!empty($href)) {
$href = $href . '/movie/' . $movieID;
$href = str_replace("//movie/", "/movie/", $href);
}
$details = array(
"topTitle" => $movieName,
"bottomTitle" => $alternativeTitles,
"status" => $child['status'],
"overview" => $child['overview'],
"runtime" => $child['runtime'],
"image" => $banner,
"ratings" => $child['ratings']['value'] ?? 0,
"videoQuality" => $child["hasFile"] ? @$child['movieFile']['quality']['quality']['name'] : "unknown",
"audioChannels" => $child["hasFile"] ? @$child['movieFile']['mediaInfo']['audioChannels'] : "unknown",
"audioCodec" => $child["hasFile"] ? @$child['movieFile']['mediaInfo']['audioFormat'] : "unknown",
"videoCodec" => $child["hasFile"] ? @$child['movieFile']['mediaInfo']['videoCodec'] : "unknown",
"size" => $child["hasFile"] ? @$child['movieFile']['size'] : "unknown",
"genres" => $child['genres'],
"year" => $child['year'] ?? '',
"studio" => $child['studio'] ?? '',
"href" => strtolower($href),
"icon" => "/plugins/images/tabs/radarr.png",
"frame" => $this->config['radarrFrameTarget'],
"showLink" => $this->config['radarrIcon']
);
array_push($gotCalendar, array(
"id" => "Radarr-" . $number . "-" . $i,
"title" => $movieName,
"start" => $releaseDate,
"className" => "inline-popups bg-calendar movieID--" . $movieID,
"imagetype" => "film " . $downloaded,
"imagetypeFilter" => "film",
"downloadFilter" => $downloaded,
"releaseType" => $type,
"bgColor" => str_replace('text', 'bg', $downloaded),
"details" => $details
));
}
}
if ($i != 0) {
return $gotCalendar;
}
return false;
}
}

View File

@@ -0,0 +1,299 @@
<?php
trait RTorrentHomepageItem
{
public function rTorrentSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'rTorrent',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/rTorrent.png',
'category' => 'Downloader',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$xmlStatus = (extension_loaded('xmlrpc')) ? 'Installed' : 'Not Installed';
$homepageSettings = [
'debug' => true,
'settings' => [
'FYI' => [
$this->settingsOption('html', null, ['label' => '', 'override' => 12,
'html' => '
<div class="row">
<div class="col-lg-12">
<div class="panel panel-info">
<div class="panel-heading">
<span lang="en">ATTENTION</span>
</div>
<div class="panel-wrapper collapse in" aria-expanded="true">
<div class="panel-body">
<h4 lang="en">This module requires XMLRPC</h4>
<span lang="en">Status: [ <b>' . $xmlStatus . '</b> ]</span>
<br/></br>
<span lang="en">
<h4><b>Note about API URL</b></h4>
Organizr appends the url with <code>/RPC2</code> unless the URL ends in <code>.php</code><br/>
<h5>Possible URLs:</h5>
<li>http://localhost:8080</li>
<li>https://domain.site/xmlrpc.php</li>
<li>https://seedbox.site/rutorrent/plugins/httprpc/action.php</li>
</span>
</div>
</div>
</div>
</div>
</div>
'
]),
],
'Enable' => [
$this->settingsOption('enable', 'homepagerTorrentEnabled'),
$this->settingsOption('auth', 'homepagerTorrentAuth'),
],
'Connection' => [
$this->settingsOption('url', 'rTorrentURL'),
$this->settingsOption('input', 'rTorrentURLOverride', ['label' => 'rTorrent API URL Override', 'help' => 'Only use if you cannot connect. Please make sure to use local IP address and port - You also may use local dns name too.', 'placeholder' => 'http(s)://hostname:port/xmlrpc']),
$this->settingsOption('username', 'rTorrentUsername'),
$this->settingsOption('password', 'rTorrentPassword'),
$this->settingsOption('disable-cert-check', 'rTorrentDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'rTorrentUseCustomCertificate'),
],
'Misc Options' => [
$this->settingsOption('hide-seeding', 'rTorrentHideSeeding'),
$this->settingsOption('hide-completed', 'rTorrentHideCompleted'),
$this->settingsOption('select', 'rTorrentSortOrder', ['label' => 'Order', 'options' => $this->rTorrentSortOptions()]),
$this->settingsOption('limit', 'rTorrentLimit'),
$this->settingsOption('multiple', 'rTorrentIgnoreLabel', ['label' => 'Ignore Torrent with Label(s)']),
$this->settingsOption('refresh', 'rTorrentRefresh'),
$this->settingsOption('combine', 'rTorrentCombine'),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'rtorrent'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionRTorrent()
{
if (empty($this->config['rTorrentURL']) && empty($this->config['rTorrentURLOverride'])) {
$this->setAPIResponse('error', 'rTorrent URL is not defined', 422);
return false;
}
try {
$url = $this->rTorrentURL();
$options = $this->requestOptions($url, null, $this->config['rTorrentDisableCertCheck'], $this->config['rTorrentUseCustomCertificate']);
if ($this->config['rTorrentUsername'] !== '' && $this->decrypt($this->config['rTorrentPassword']) !== '') {
$credentials = array('auth' => new Requests_Auth_Digest(array($this->config['rTorrentUsername'], $this->decrypt($this->config['rTorrentPassword']))));
$options = array_merge($options, $credentials);
}
$data = xmlrpc_encode_request("system.listMethods", null);
$response = Requests::post($url, [], $data, $options);
if ($response->success) {
$methods = xmlrpc_decode(str_replace('i8>', 'i4>', $response->body));
if (count($methods) !== 0) {
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
}
}
$this->setAPIResponse('error', 'rTorrent error occurred', 500);
return false;
} catch
(Requests_Exception $e) {
$this->setLoggerChannel('rTorrent')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function rTorrentHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepagerTorrentEnabled'
],
'auth' => [
'homepagerTorrentAuth'
],
'not_empty' => []
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderrTorrent()
{
if ($this->homepageItemPermissions($this->rTorrentHomepagePermissions('main'))) {
$loadingBox = ($this->config['rTorrentCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
$builder = ($this->config['rTorrentCombine']) ? 'buildDownloaderCombined(\'rTorrent\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("rTorrent"));';
return '
<div id="' . __FUNCTION__ . '">
' . $loadingBox . '
<script>
// homepageOrderrTorrent
' . $builder . '
homepageDownloader("rTorrent", "' . $this->config['rTorrentRefresh'] . '");
// End homepageOrderrTorrent
</script>
</div>
';
}
}
public function checkOverrideURL($url, $override)
{
if (strpos($override, $url) !== false) {
return $override;
} else {
return $url . $override;
}
}
public function rTorrentStatus($completed, $state, $status)
{
if ($completed && $state && $status == 'seed') {
$state = 'Seeding';
} elseif (!$completed && !$state && $status == 'leech') {
$state = 'Stopped';
} elseif (!$completed && $state && $status == 'leech') {
$state = 'Downloading';
} elseif ($completed && !$state && $status == 'seed') {
$state = 'Finished';
} elseif ($completed && !$state && $status == 'leech') {
$state = 'Finished';
}
return ($state) ?: $status;
}
public function getRTorrentHomepageQueue()
{
if (empty($this->config['rTorrentURL']) && empty($this->config['rTorrentURLOverride'])) {
$this->setAPIResponse('error', 'rTorrent URL is not defined', 422);
return false;
}
if (!$this->homepageItemPermissions($this->rTorrentHomepagePermissions('main'), true)) {
return false;
}
try {
if ($this->config['rTorrentLimit'] == '0') {
$this->config['rTorrentLimit'] = '1000';
}
$torrents = array();
$url = $this->rTorrentURL();
$options = $this->requestOptions($url, $this->config['rTorrentRefresh'], $this->config['rTorrentDisableCertCheck'], $this->config['rTorrentUseCustomCertificate']);
if ($this->config['rTorrentUsername'] !== '' && $this->decrypt($this->config['rTorrentPassword']) !== '') {
$credentials = array('auth' => new Requests_Auth_Digest(array($this->config['rTorrentUsername'], $this->decrypt($this->config['rTorrentPassword']))));
$options = array_merge($options, $credentials);
}
$data = xmlrpc_encode_request("d.multicall2", array(
"",
"main",
"d.name=",
"d.base_path=",
"d.up.total=",
"d.size_bytes=",
"d.down.total=",
"d.completed_bytes=",
"d.connection_current=",
"d.down.rate=",
"d.up.rate=",
"d.timestamp.started=",
"d.state=",
"d.group.name=",
"d.hash=",
"d.complete=",
"d.ratio=",
"d.chunk_size=",
"f.size_bytes=",
"f.size_chunks=",
"f.completed_chunks=",
"d.custom=",
"d.custom1=",
"d.custom2=",
"d.custom3=",
"d.custom4=",
"d.custom5=",
), array());
$response = Requests::post($url, [], $data, $options);
if ($response->success) {
$torrentList = xmlrpc_decode(str_replace('i8>', 'string>', $response->body));
if (is_array($torrentList)) {
foreach ($torrentList as $key => $value) {
$tempStatus = $this->rTorrentStatus($value[13], $value[10], $value[6]);
if ($tempStatus == 'Seeding' && $this->config['rTorrentHideSeeding']) {
//do nothing
} elseif ($tempStatus == 'Finished' && $this->config['rTorrentHideCompleted']) {
//do nothing
} elseif (stripos($this->config['rTorrentIgnoreLabel'], $value[20]) !== false) {
//do nothing
} else {
$torrents[$key] = array(
'name' => $value[0],
'base' => $value[1],
'upTotal' => $value[2],
'size' => $value[3],
'downTotal' => $value[4],
'downloaded' => $value[5],
'connectionState' => $value[6],
'leech' => $value[7],
'seed' => $value[8],
'date' => $value[9],
'state' => ($value[10]) ? 'on' : 'off',
'group' => $value[11],
'hash' => $value[12],
'complete' => ($value[13]) ? 'yes' : 'no',
'ratio' => $value[14],
'label' => $value[20],
'status' => $tempStatus,
'temp' => $value[16] . ' - ' . $value[17] . ' - ' . $value[18],
'custom' => $value[19] . ' - ' . $value[20] . ' - ' . $value[21],
'custom2' => $value[22] . ' - ' . $value[23] . ' - ' . $value[24],
);
}
}
}
if (count($torrents) !== 0) {
usort($torrents, function ($a, $b) {
$direction = substr($this->config['rTorrentSortOrder'], -1);
$sort = substr($this->config['rTorrentSortOrder'], 0, strlen($this->config['rTorrentSortOrder']) - 1);
switch ($direction) {
case 'a':
return $a[$sort] <=> $b[$sort];
case 'd':
return $b[$sort] <=> $a[$sort];
default:
return $b['date'] <=> $a['date'];
}
});
$torrents = array_slice($torrents, 0, $this->config['rTorrentLimit']);
}
$api['content']['queueItems'] = $torrents;
$api['content']['historyItems'] = false;
}
} catch
(Requests_Exception $e) {
$this->setLoggerChannel('rTorrent')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = $api['content'] ?? false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
/**
* @return string
*/
public function rTorrentURL(): string
{
$digest = (empty($this->config['rTorrentURLOverride'])) ? $this->qualifyURL($this->config['rTorrentURL'], true) : $this->qualifyURL($this->checkOverrideURL($this->config['rTorrentURL'], $this->config['rTorrentURLOverride']), true);
$extraPath = (strpos($this->config['rTorrentURL'], '.php') !== false) ? '' : '/RPC2';
$extraPath = (empty($this->config['rTorrentURLOverride'])) ? $extraPath : '';
return $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $extraPath;
}
}

View File

@@ -0,0 +1,214 @@
<?php
trait SabNZBdHomepageItem
{
public function sabNZBdSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'SabNZBD',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/sabnzbd.png',
'category' => 'Downloader',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageSabnzbdEnabled'),
$this->settingsOption('auth', 'homepageSabnzbdAuth'),
],
'Connection' => [
$this->settingsOption('url', 'sabnzbdURL'),
$this->settingsOption('token', 'sabnzbdToken'),
$this->settingsOption('disable-cert-check', 'sabnzbdDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'sabnzbdUseCustomCertificate'),
],
'API SOCKS' => [
$this->settingsOption('socks', 'sabnzbd'),
$this->settingsOption('blank'),
$this->settingsOption('enable', 'sabnzbdSocksEnabled'),
$this->settingsOption('auth', 'sabnzbdSocksAuth'),
],
'Misc Options' => [
$this->settingsOption('refresh', 'sabnzbdRefresh'),
$this->settingsOption('combine', 'sabnzbdCombine'),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'sabnzbd'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionSabNZBd()
{
$this->setLoggerChannel('Sabnzbd Homepage');
$this->logger->debug('Starting API Connection Test');
if (!empty($this->config['sabnzbdURL']) && !empty($this->config['sabnzbdToken'])) {
$url = $this->qualifyURL($this->config['sabnzbdURL']);
$url = $url . '/api?mode=queue&output=json&apikey=' . $this->config['sabnzbdToken'];
try {
$options = $this->requestOptions($url, null, $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
$response = Requests::get($url, [], $options);
if ($response->success) {
$data = json_decode($response->body, true);
$status = 'success';
$responseCode = 200;
$message = 'API Connection succeeded';
if (isset($data['error'])) {
$status = 'error';
$responseCode = 500;
$message = $data['error'];
}
$this->setAPIResponse($status, $message, $responseCode, $data);
$this->logger->debug('API Connection Test was successful');
return true;
} else {
$this->setAPIResponse('error', $response->body, 500);
$this->logger->debug('API Connection Test was unsuccessful');
return false;
}
} catch (Requests_Exception $e) {
$this->logger->critical($e, [$url]);
$this->setResponse(500, $e->getMessage());
return false;
}
} else {
$this->logger->debug('URL and/or Token not setup');
$this->setAPIResponse('error', 'URL and/or Token not setup', 422);
return 'URL and/or Token not setup';
}
}
public function sabNZBdHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageSabnzbdEnabled'
],
'auth' => [
'homepageSabnzbdAuth'
],
'not_empty' => [
'sabnzbdURL',
'sabnzbdToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrdersabnzbd()
{
if ($this->homepageItemPermissions($this->sabNZBdHomepagePermissions('main'))) {
$loadingBox = ($this->config['sabnzbdCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
$builder = ($this->config['sabnzbdCombine']) ? 'buildDownloaderCombined(\'sabnzbd\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("sabnzbd"));';
return '
<div id="' . __FUNCTION__ . '">
' . $loadingBox . '
<script>
// homepageOrdersabnzbd
' . $builder . '
homepageDownloader("sabnzbd", "' . $this->config['sabnzbdRefresh'] . '");
// End homepageOrdersabnzbd
</script>
</div>
';
}
}
public function getSabNZBdHomepageQueue()
{
$this->setLoggerChannel('Sabnzbd Homepage');
if (!$this->homepageItemPermissions($this->sabNZBdHomepagePermissions('main'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['sabnzbdURL']);
$url = $url . '/api?mode=queue&output=json&apikey=' . $this->config['sabnzbdToken'];
try {
$options = $this->requestOptions($url, $this->config['sabnzbdRefresh'], $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
$response = Requests::get($url, [], $options);
if ($response->success) {
$api['content']['queueItems'] = json_decode($response->body, true);
}
} catch (Requests_Exception $e) {
$this->logger->critical($e, [$url]);
$this->setResponse(500, $e->getMessage());
return false;
};
$url = $this->qualifyURL($this->config['sabnzbdURL']);
$url = $url . '/api?mode=history&output=json&limit=100&apikey=' . $this->config['sabnzbdToken'];
try {
$options = $this->requestOptions($url, $this->config['sabnzbdRefresh'], $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
$response = Requests::get($url, array(), $options);
if ($response->success) {
$api['content']['historyItems'] = json_decode($response->body, true);
}
} catch (Requests_Exception $e) {
$this->logger->critical($e, [$url]);
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = isset($api['content']) ? $api['content'] : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
public function pauseSabNZBdQueue($target = null)
{
$this->setLoggerChannel('Sabnzbd Homepage');
if (!$this->homepageItemPermissions($this->sabNZBdHomepagePermissions('main'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['sabnzbdURL']);
$id = ($target !== '' && $target !== 'main' && isset($target)) ? 'mode=queue&name=pause&value=' . $target . '&' : 'mode=pause';
$url = $url . '/api?' . $id . '&output=json&apikey=' . $this->config['sabnzbdToken'];
try {
$options = $this->requestOptions($url, $this->config['sabnzbdRefresh'], $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
$response = Requests::get($url, [], $options);
if ($response->success) {
$api['content'] = json_decode($response->body, true);
}
} catch (Requests_Exception $e) {
$this->logger->critical($e, [$url]);
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = isset($api['content']) ? $api['content'] : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
public function resumeSabNZBdQueue($target = null)
{
$this->setLoggerChannel('Sabnzbd Homepage');
if (!$this->homepageItemPermissions($this->sabNZBdHomepagePermissions('main'), true)) {
return false;
}
$url = $this->qualifyURL($this->config['sabnzbdURL']);
$id = ($target !== '' && $target !== 'main' && isset($target)) ? 'mode=queue&name=resume&value=' . $target . '&' : 'mode=resume';
$url = $url . '/api?' . $id . '&output=json&apikey=' . $this->config['sabnzbdToken'];
try {
$options = $this->requestOptions($url, $this->config['sabnzbdRefresh'], $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
$response = Requests::get($url, [], $options);
if ($response->success) {
$api['content'] = json_decode($response->body, true);
}
} catch (Requests_Exception $e) {
$this->logger->critical($e, [$url]);
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = isset($api['content']) ? $api['content'] : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
}

View File

@@ -0,0 +1,432 @@
<?php
trait SickRageHomepageItem
{
public function sickrageSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'SickRage',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/sickrage.png',
'category' => 'PVR',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageSickrageEnabled'),
$this->settingsOption('auth', 'homepageSickrageAuth'),
],
'Connection' => [
$this->settingsOption('url', 'sickrageURL'),
$this->settingsOption('token', 'sickrageToken'),
$this->settingsOption('disable-cert-check', 'sickrageDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'sickrageUseCustomCertificate'),
],
'Calendar' => [
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
$this->settingsOption('calendar-default-view', 'calendarDefault'),
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
$this->settingsOption('calendar-locale', 'calendarLocale'),
$this->settingsOption('calendar-limit', 'calendarLimit'),
$this->settingsOption('refresh', 'calendarRefresh'),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'sickrage'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionSickRage()
{
if (empty($this->config['sickrageURL'])) {
$this->setAPIResponse('error', 'SickRage URL is not defined', 422);
return false;
}
if (empty($this->config['sickrageToken'])) {
$this->setAPIResponse('error', 'SickRage Token is not defined', 422);
return false;
}
$failed = false;
$errors = '';
$list = $this->csvHomepageUrlToken($this->config['sickrageURL'], $this->config['sickrageToken']);
foreach ($list as $key => $value) {
try {
$options = $this->requestOptions($value['url'], null, $this->config['sickrageDisableCertCheck'], $this->config['sickrageUseCustomCertificate']);
$downloader = new Kryptonit3\SickRage\SickRage($value['url'], $value['token'], null, null, $options);
$results = $downloader->sb();
$downloadList = json_decode($results, true);
if (is_array($downloadList) || is_object($downloadList)) {
$queue = (array_key_exists('error', $downloadList)) ? $downloadList['error']['msg'] : $downloadList;
if (!is_array($queue)) {
$ip = $value['url'];
$errors .= $ip . ': ' . $queue;
$failed = true;
}
} else {
$ip = $value['url'];
$errors .= $ip . ': Response was not JSON';
$failed = true;
}
} catch (Exception $e) {
$failed = true;
$ip = $value['url'];
$errors .= $ip . ': ' . $e->getMessage();
$this->setLoggerChannel('SickRage')->error($e);
}
}
if ($failed) {
$this->setAPIResponse('error', $errors, 500);
return false;
} else {
$this->setAPIResponse('success', null, 200);
return true;
}
}
public function sickrageHomepagePermissions($key = null)
{
$permissions = [
'calendar' => [
'enabled' => [
'homepageSickrageEnabled'
],
'auth' => [
'homepageSickrageAuth'
],
'not_empty' => [
'sickrageURL',
'sickrageToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function getSickRageCalendar($startDate = null, $endDate = null)
{
if (!$this->homepageItemPermissions($this->sickrageHomepagePermissions('calendar'), true)) {
return false;
}
$calendarItems = array();
$list = $this->csvHomepageUrlToken($this->config['sickrageURL'], $this->config['sickrageToken']);
foreach ($list as $key => $value) {
try {
$options = $this->requestOptions($value['url'], null, $this->config['sickrageDisableCertCheck'], $this->config['sickrageUseCustomCertificate']);
$downloader = new Kryptonit3\SickRage\SickRage($value['url'], $value['token'], null, null, $options);
$sickrageFuture = $this->formatSickrageCalendarWanted($downloader->future(), $key);
$sickrageHistory = $this->formatSickrageCalendarHistory($downloader->history("100", "downloaded"), $key);
if (!empty($sickrageFuture)) {
$calendarItems = array_merge($calendarItems, $sickrageFuture);
}
if (!empty($sickrageHistory)) {
$calendarItems = array_merge($calendarItems, $sickrageHistory);
}
} catch (Exception $e) {
$this->setLoggerChannel('SickRage')->error($e);
}
}
$this->setAPIResponse('success', null, 200, $calendarItems);
return $calendarItems;
}
public function formatSickrageCalendarWanted($array, $number)
{
$array = json_decode($array, true);
$gotCalendar = array();
$i = 0;
foreach ($array['data']['missed'] as $child) {
$i++;
$seriesName = $child['show_name'];
$seriesID = $child['tvdbid'];
$episodeID = $child['tvdbid'];
$episodeAirDate = $child['airdate'];
$episodeAirDateTime = explode(" ", $child['airs']);
$episodeAirDateTime = date("H:i:s", strtotime($episodeAirDateTime[1] . $episodeAirDateTime[2]));
$episodeAirDate = strtotime($episodeAirDate . $episodeAirDateTime);
$episodeAirDate = date("Y-m-d H:i:s", $episodeAirDate);
if (new DateTime() < new DateTime($episodeAirDate)) {
$unaired = true;
}
$downloaded = "0";
if ($downloaded == "0" && isset($unaired)) {
$downloaded = "text-info";
} elseif ($downloaded == "1") {
$downloaded = "text-success";
} else {
$downloaded = "text-danger";
}
$bottomTitle = 'S' . sprintf("%02d", $child['season']) . 'E' . sprintf("%02d", $child['episode']) . ' - ' . $child['ep_name'];
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$cacheFile = $cacheDirectory . $seriesID . '.jpg';
$fanart = "/plugins/images/homepage/no-np.png";
if (file_exists($cacheFile)) {
$fanart = 'data/cache/' . $seriesID . '.jpg';
unset($cacheFile);
}
$details = array(
"seasonCount" => "",
"status" => $child['show_status'],
"topTitle" => $seriesName,
"bottomTitle" => $bottomTitle,
"overview" => isset($child['ep_plot']) ? $child['ep_plot'] : '',
"runtime" => "",
"image" => $fanart,
"ratings" => "",
"videoQuality" => isset($child["quality"]) ? $child["quality"] : "",
"audioChannels" => "",
"audioCodec" => "",
"videoCodec" => "",
"size" => "",
"genres" => "",
);
array_push($gotCalendar, array(
"id" => "Sick-" . $number . "-Miss-" . $i,
"title" => $seriesName,
"start" => $episodeAirDate,
"className" => "inline-popups bg-calendar calendar-item tvID--" . $episodeID,
"imagetype" => "tv " . $downloaded,
"imagetypeFilter" => "tv",
"downloadFilter" => $downloaded,
"bgColor" => str_replace('text', 'bg', $downloaded),
"details" => $details,
));
}
foreach ($array['data']['today'] as $child) {
$i++;
$seriesName = $child['show_name'];
$seriesID = $child['tvdbid'];
$episodeID = $child['tvdbid'];
$episodeAirDate = $child['airdate'];
$episodeAirDateTime = explode(" ", $child['airs']);
$episodeAirDateTime = date("H:i:s", strtotime($episodeAirDateTime[1] . $episodeAirDateTime[2]));
$episodeAirDate = strtotime($episodeAirDate . $episodeAirDateTime);
$episodeAirDate = date("Y-m-d H:i:s", $episodeAirDate);
if (new DateTime() < new DateTime($episodeAirDate)) {
$unaired = true;
}
$downloaded = "0";
if ($downloaded == "0" && isset($unaired)) {
$downloaded = "text-info";
} elseif ($downloaded == "1") {
$downloaded = "text-success";
} else {
$downloaded = "text-danger";
}
$bottomTitle = 'S' . sprintf("%02d", $child['season']) . 'E' . sprintf("%02d", $child['episode']) . ' - ' . $child['ep_name'];
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$cacheFile = $cacheDirectory . $seriesID . '.jpg';
$fanart = "/plugins/images/homepage/no-np.png";
if (file_exists($cacheFile)) {
$fanart = 'data/cache/' . $seriesID . '.jpg';
unset($cacheFile);
}
$details = array(
"seasonCount" => "",
"status" => $child['show_status'],
"topTitle" => $seriesName,
"bottomTitle" => $bottomTitle,
"overview" => isset($child['ep_plot']) ? $child['ep_plot'] : '',
"runtime" => "",
"image" => $fanart,
"ratings" => "",
"videoQuality" => isset($child["quality"]) ? $child["quality"] : "",
"audioChannels" => "",
"audioCodec" => "",
"videoCodec" => "",
"size" => "",
"genres" => "",
);
array_push($gotCalendar, array(
"id" => "Sick-" . $number . "-Today-" . $i,
"title" => $seriesName,
"start" => $episodeAirDate,
"className" => "inline-popups bg-calendar calendar-item tvID--" . $episodeID,
"imagetype" => "tv " . $downloaded,
"imagetypeFilter" => "tv",
"downloadFilter" => $downloaded,
"bgColor" => str_replace('text', 'bg', $downloaded),
"details" => $details,
));
}
foreach ($array['data']['soon'] as $child) {
$i++;
$seriesName = $child['show_name'];
$seriesID = $child['tvdbid'];
$episodeID = $child['tvdbid'];
$episodeAirDate = $child['airdate'];
$episodeAirDateTime = explode(" ", $child['airs']);
$episodeAirDateTime = date("H:i:s", strtotime($episodeAirDateTime[1] . $episodeAirDateTime[2]));
$episodeAirDate = strtotime($episodeAirDate . $episodeAirDateTime);
$episodeAirDate = date("Y-m-d H:i:s", $episodeAirDate);
if (new DateTime() < new DateTime($episodeAirDate)) {
$unaired = true;
}
$downloaded = "0";
if ($downloaded == "0" && isset($unaired)) {
$downloaded = "text-info";
} elseif ($downloaded == "1") {
$downloaded = "text-success";
} else {
$downloaded = "text-danger";
}
$bottomTitle = 'S' . sprintf("%02d", $child['season']) . 'E' . sprintf("%02d", $child['episode']) . ' - ' . $child['ep_name'];
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$cacheFile = $cacheDirectory . $seriesID . '.jpg';
$fanart = "/plugins/images/homepage/no-np.png";
if (file_exists($cacheFile)) {
$fanart = 'data/cache/' . $seriesID . '.jpg';
unset($cacheFile);
}
$details = array(
"seasonCount" => "",
"status" => $child['show_status'],
"topTitle" => $seriesName,
"bottomTitle" => $bottomTitle,
"overview" => isset($child['ep_plot']) ? $child['ep_plot'] : '',
"runtime" => "",
"image" => $fanart,
"ratings" => "",
"videoQuality" => isset($child["quality"]) ? $child["quality"] : "",
"audioChannels" => "",
"audioCodec" => "",
"videoCodec" => "",
"size" => "",
"genres" => "",
);
array_push($gotCalendar, array(
"id" => "Sick-" . $number . "-Soon-" . $i,
"title" => $seriesName,
"start" => $episodeAirDate,
"className" => "inline-popups bg-calendar calendar-item tvID--" . $episodeID,
"imagetype" => "tv " . $downloaded,
"imagetypeFilter" => "tv",
"downloadFilter" => $downloaded,
"bgColor" => str_replace('text', 'bg', $downloaded),
"details" => $details,
));
}
foreach ($array['data']['later'] as $child) {
$i++;
$seriesName = $child['show_name'];
$seriesID = $child['tvdbid'];
$episodeID = $child['tvdbid'];
$episodeAirDate = $child['airdate'];
$episodeAirDateTime = explode(" ", $child['airs']);
$episodeAirDateTime = date("H:i:s", strtotime($episodeAirDateTime[1] . $episodeAirDateTime[2]));
$episodeAirDate = strtotime($episodeAirDate . $episodeAirDateTime);
$episodeAirDate = date("Y-m-d H:i:s", $episodeAirDate);
if (new DateTime() < new DateTime($episodeAirDate)) {
$unaired = true;
}
$downloaded = "0";
if ($downloaded == "0" && isset($unaired)) {
$downloaded = "text-info";
} elseif ($downloaded == "1") {
$downloaded = "text-success";
} else {
$downloaded = "text-danger";
}
$bottomTitle = 'S' . sprintf("%02d", $child['season']) . 'E' . sprintf("%02d", $child['episode']) . ' - ' . $child['ep_name'];
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$cacheFile = $cacheDirectory . $seriesID . '.jpg';
$fanart = "/plugins/images/homepage/no-np.png";
if (file_exists($cacheFile)) {
$fanart = 'data/cache/' . $seriesID . '.jpg';
unset($cacheFile);
}
$details = array(
"seasonCount" => "",
"status" => $child['show_status'],
"topTitle" => $seriesName,
"bottomTitle" => $bottomTitle,
"overview" => isset($child['ep_plot']) ? $child['ep_plot'] : '',
"runtime" => "",
"image" => $fanart,
"ratings" => "",
"videoQuality" => isset($child["quality"]) ? $child["quality"] : "",
"audioChannels" => "",
"audioCodec" => "",
"videoCodec" => "",
"size" => "",
"genres" => "",
);
array_push($gotCalendar, array(
"id" => "Sick-" . $number . "-Later-" . $i,
"title" => $seriesName,
"start" => $episodeAirDate,
"className" => "inline-popups bg-calendar calendar-item tvID--" . $episodeID,
"imagetype" => "tv " . $downloaded,
"imagetypeFilter" => "tv",
"downloadFilter" => $downloaded,
"bgColor" => str_replace('text', 'bg', $downloaded),
"details" => $details,
));
}
if ($i != 0) {
return $gotCalendar;
}
return false;
}
public function formatSickrageCalendarHistory($array, $number)
{
$array = json_decode($array, true);
$gotCalendar = array();
$i = 0;
foreach ($array['data'] as $child) {
$i++;
$seriesName = $child['show_name'];
$seriesID = $child['tvdbid'];
$episodeID = $child['tvdbid'];
$episodeAirDate = $child['date'];
$downloaded = "text-success";
$bottomTitle = 'S' . sprintf("%02d", $child['season']) . 'E' . sprintf("%02d", $child['episode']);
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$cacheFile = $cacheDirectory . $seriesID . '.jpg';
$fanart = "/plugins/images/homepage/no-np.png";
if (file_exists($cacheFile)) {
$fanart = 'data/cache/' . $seriesID . '.jpg';
unset($cacheFile);
}
$details = array(
"seasonCount" => "",
"status" => $child['status'],
"topTitle" => $seriesName,
"bottomTitle" => $bottomTitle,
"overview" => '',
"runtime" => isset($child['series']['runtime']) ? $child['series']['runtime'] : 30,
"image" => $fanart,
"ratings" => isset($child['series']['ratings']['value']) ? $child['series']['ratings']['value'] : "unknown",
"videoQuality" => isset($child["quality"]) ? $child['quality'] : "unknown",
"audioChannels" => "",
"audioCodec" => "",
"videoCodec" => "",
"size" => "",
"genres" => "",
);
array_push($gotCalendar, array(
"id" => "Sick-" . $number . "-History-" . $i,
"title" => $seriesName,
"start" => $episodeAirDate,
"className" => "inline-popups bg-calendar calendar-item tvID--" . $episodeID,
"imagetype" => "tv " . $downloaded,
"imagetypeFilter" => "tv",
"downloadFilter" => $downloaded,
"bgColor" => str_replace('text', 'bg', $downloaded),
"details" => $details,
));
}
if ($i != 0) {
return $gotCalendar;
}
return false;
}
}

View File

@@ -0,0 +1,345 @@
<?php
trait SonarrHomepageItem
{
public function sonarrSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Sonarr',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/sonarr.png',
'category' => 'PVR',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'docs' => $this->docs('features/homepage/sonarr-homepage-item'),
'debug' => true,
'settings' => [
'About' => [
$this->settingsOption('about', 'Sonarr', ['about' => 'This item allows access to Sonarr\'s calendar data and aggregates it to Organizr\'s calendar. Along with that you also have the Downloader function that allow access to Sonarr\'s queue. The last item that is included is the API SOCKS function which acts as a middleman between API\'s which is useful if you are not port forwarding or reverse proxying Sonarr.']),
],
'Enable' => [
$this->settingsOption('enable', 'homepageSonarrEnabled'),
$this->settingsOption('auth', 'homepageSonarrAuth'),
],
'Connection' => [
$this->settingsOption('multiple-url', 'sonarrURL'),
$this->settingsOption('multiple-token', 'sonarrToken'),
$this->settingsOption('disable-cert-check', 'sonarrDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'sonarrUseCustomCertificate'),
],
'API SOCKS' => [
$this->settingsOption('socks', 'sonarr'),
$this->settingsOption('blank'),
$this->settingsOption('enable', 'sonarrSocksEnabled'),
$this->settingsOption('auth', 'sonarrSocksAuth'),
],
'Queue' => [
$this->settingsOption('enable', 'homepageSonarrQueueEnabled'),
$this->settingsOption('auth', 'homepageSonarrQueueAuth'),
$this->settingsOption('combine', 'homepageSonarrQueueCombine'),
$this->settingsOption('refresh', 'homepageSonarrQueueRefresh'),
],
'Calendar' => [
$this->settingsOption('calendar-start', 'calendarStart'),
$this->settingsOption('calendar-end', 'calendarEnd'),
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
$this->settingsOption('calendar-default-view', 'calendarDefault'),
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
$this->settingsOption('calendar-locale', 'calendarLocale'),
$this->settingsOption('calendar-limit', 'calendarLimit'),
$this->settingsOption('refresh', 'calendarRefresh'),
$this->settingsOption('blank'),
$this->settingsOption('switch', 'sonarrUnmonitored', ['label' => 'Show Unmonitored']),
$this->settingsOption('blank', '', ['type' => 'html', 'html' => '<hr />']),
$this->settingsOption('blank', '', ['type' => 'html', 'html' => '<hr />']),
$this->settingsOption('enable', 'sonarrIcon', ['label' => 'Show Sonarr Icon']),
$this->settingsOption('calendar-link-url', 'sonarrCalendarLink'),
$this->settingsOption('blank'),
$this->settingsOption('calendar-frame-target', 'sonarrFrameTarget')
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'sonarr'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionSonarr()
{
if (empty($this->config['sonarrURL'])) {
$this->setAPIResponse('error', 'Sonarr URL is not defined', 422);
return false;
}
if (empty($this->config['sonarrToken'])) {
$this->setAPIResponse('error', 'Sonarr Token is not defined', 422);
return false;
}
$failed = false;
$errors = '';
$list = $this->csvHomepageUrlToken($this->config['sonarrURL'], $this->config['sonarrToken']);
foreach ($list as $key => $value) {
try {
$options = $this->requestOptions($value['url'], null, $this->config['sonarrDisableCertCheck'], $this->config['sonarrUseCustomCertificate']);
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'sonarr', null, null, $options);
$results = $downloader->getRootFolder();
$downloadList = json_decode($results, true);
if (is_array($downloadList) || is_object($downloadList)) {
$queue = (array_key_exists('error', $downloadList)) ? $downloadList['error']['msg'] : $downloadList;
if (!is_array($queue)) {
$ip = $value['url'];
$errors .= $ip . ': ' . $queue;
$failed = true;
}
} else {
$ip = $value['url'];
$errors .= $ip . ': Response was not JSON';
$failed = true;
}
} catch (Exception $e) {
$failed = true;
$ip = $value['url'];
$errors .= $ip . ': ' . $e->getMessage();
$this->setLoggerChannel('Sonarr')->error($e);
}
}
if ($failed) {
$this->setAPIResponse('error', $errors, 500);
return false;
} else {
$this->setAPIResponse('success', null, 200);
return true;
}
}
public function sonarrHomepagePermissions($key = null)
{
$permissions = [
'calendar' => [
'enabled' => [
'homepageSonarrEnabled'
],
'auth' => [
'homepageSonarrAuth'
],
'not_empty' => [
'sonarrURL',
'sonarrToken'
]
],
'queue' => [
'enabled' => [
'homepageSonarrEnabled',
'homepageSonarrQueueEnabled'
],
'auth' => [
'homepageSonarrAuth',
'homepageSonarrQueueAuth'
],
'not_empty' => [
'sonarrURL',
'sonarrToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderSonarrQueue()
{
if ($this->homepageItemPermissions($this->sonarrHomepagePermissions('queue'))) {
$loadingBox = ($this->config['homepageSonarrQueueCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
$builder = ($this->config['homepageSonarrQueueCombine']) ? 'buildDownloaderCombined(\'sonarr\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("sonarr"));';
return '
<div id="' . __FUNCTION__ . '">
' . $loadingBox . '
<script>
// homepageOrderSonarrQueue
' . $builder . '
homepageDownloader("sonarr", "' . $this->config['homepageSonarrQueueRefresh'] . '");
// End homepageOrderSonarrQueue
</script>
</div>
';
}
}
public function getSonarrQueue()
{
if (!$this->homepageItemPermissions($this->sonarrHomepagePermissions('queue'), true)) {
return false;
}
$queueItems = array();
$list = $this->csvHomepageUrlToken($this->config['sonarrURL'], $this->config['sonarrToken']);
foreach ($list as $key => $value) {
try {
$options = $this->requestOptions($value['url'], $this->config['homepageSonarrQueueRefresh'], $this->config['sonarrDisableCertCheck'], $this->config['sonarrUseCustomCertificate']);
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'sonarr', null, null, $options);
$results = $downloader->getQueue();
$downloadList = json_decode($results, true);
if (is_array($downloadList) || is_object($downloadList)) {
$queue = (array_key_exists('error', $downloadList)) ? [] : $downloadList;
$queue = $queue['records'] ?? $queue;
} else {
$queue = [];
}
if (!empty($queue)) {
$queueItems = array_merge($queueItems, $queue);
}
} catch (Exception $e) {
$this->setLoggerChannel('Sonarr')->error($e);
}
}
$api['content']['queueItems'] = $queueItems;
$api['content']['historyItems'] = false;
$api['content'] = $api['content'] ?? false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
public function getSonarrCalendar($startDate = null, $endDate = null)
{
$startDate = ($startDate) ?? $_GET['start'] ?? date('Y-m-d', strtotime('-' . $this->config['calendarStart'] . ' days'));
$endDate = ($endDate) ?? $_GET['end'] ?? date('Y-m-d', strtotime('+' . $this->config['calendarEnd'] . ' days'));
if (!$this->homepageItemPermissions($this->sonarrHomepagePermissions('calendar'), true)) {
return false;
}
if ($this->demo) {
return $this->demoData('sonarr/calendar.json');
}
$calendarItems = array();
$list = $this->csvHomepageUrlToken($this->config['sonarrURL'], $this->config['sonarrToken']);
foreach ($list as $key => $value) {
try {
$options = $this->requestOptions($value['url'], null, $this->config['sonarrDisableCertCheck'], $this->config['sonarrUseCustomCertificate']);
$sonarr = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'sonarr', null, null, $options);
$sonarr = $sonarr->getCalendar($startDate, $endDate, $this->config['sonarrUnmonitored']);
$result = json_decode($sonarr, true);
if (is_array($result) || is_object($result)) {
$sonarrCalendar = (array_key_exists('error', $result)) ? '' : $this->formatSonarrCalendar($sonarr, $key);
} else {
$sonarrCalendar = '';
}
} catch (Exception $e) {
$this->setLoggerChannel('Sonarr')->error($e);
}
if (!empty($sonarrCalendar)) {
$calendarItems = array_merge($calendarItems, $sonarrCalendar);
}
}
$this->setAPIResponse('success', null, 200, $calendarItems);
return $calendarItems;
}
public function formatSonarrCalendar($array, $number)
{
$array = json_decode($array, true);
$gotCalendar = [];
$i = 0;
foreach ($array as $child) {
$i++;
$seriesName = $child['series']['title'];
$seriesID = $child['series']['tvdbId'];
$episodeID = $child['series']['tvdbId'];
$monitored = $child['monitored'];
if (!isset($episodeID)) {
$episodeID = "";
}
$episodeAirDate = $child['airDateUtc'];
$episodeAirDate = strtotime($episodeAirDate);
$episodeAirDate = date("Y-m-d H:i:s", $episodeAirDate);
if (new DateTime() < new DateTime($episodeAirDate)) {
$unAired = true;
}
if ($child['episodeNumber'] == "1") {
$episodePremier = "true";
} else {
$episodePremier = "false";
$date = new DateTime($episodeAirDate);
$date->add(new DateInterval("PT1S"));
$date->format(DateTime::ATOM);
$child['airDateUtc'] = gmdate('Y-m-d\TH:i:s\Z', strtotime($date->format(DateTime::ATOM)));
}
$downloaded = $child['hasFile'];
if ($downloaded == "0" && isset($unAired) && $episodePremier == "true") {
$downloaded = "text-primary animated flash";
} elseif ($downloaded == "0" && isset($unAired) && $monitored == "0") {
$downloaded = "text-dark";
} elseif ($downloaded == "0" && isset($unAired)) {
$downloaded = "text-info";
} elseif ($downloaded == "1") {
$downloaded = "text-success";
} else {
$downloaded = "text-danger";
}
$fanArt = "/plugins/images/homepage/no-np.png";
foreach ($child['series']['images'] as $image) {
if ($image['coverType'] == "fanart" && (isset($image['url']) && $image['url'] !== '')) {
$fanArt = $image['url'];
}
if ($image['coverType'] == 'fanart' && (isset($image['remoteUrl']) && $image['remoteUrl'] !== '')) {
$fanArt = $image['remoteUrl'];
}
}
if ($fanArt !== "/plugins/images/homepage/no-np.png" || (strpos($fanArt, '://') === false)) {
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
$imageURL = $fanArt;
$fanArt = 'data/cache/' . $seriesID . '.jpg';
if (!file_exists($cacheDirectory . $seriesID . '.jpg')) {
$this->cacheImage($imageURL, $seriesID);
unset($imageURL);
}
}
$bottomTitle = 'S' . sprintf("%02d", $child['seasonNumber']) . 'E' . sprintf("%02d", $child['episodeNumber']) . ' - ' . $child['title'];
$href = $this->config['sonarrCalendarLink'] ?? '';
if (empty($href) && !empty($this->config['sonarrURL'])) {
$href_arr = explode(',', $this->config['sonarrURL']);
$href = reset($href_arr);
}
if (!empty($href)) {
$href = $href . '/series/' . preg_replace('/[^A-Za-z0-9 -]/', '', str_replace('&', 'and', preg_replace('/[[:space:]]+/', '-', $seriesName)));
$href = str_replace("//series/", "/series/", $href);
}
$details = [
"seasonCount" => $child['series']['seasonCount'] ?? isset($child['series']['seasons']) ? count($child['series']['seasons']) : 0,
"status" => $child['series']['status'],
"topTitle" => $seriesName,
"bottomTitle" => $bottomTitle,
"overview" => $child['overview'] ?? '',
"runtime" => $child['series']['runtime'],
"image" => $fanArt,
"ratings" => $child['series']['ratings']['value'],
"videoQuality" => $child["hasFile"] && isset($child['episodeFile']['quality']['quality']['name']) ? $child['episodeFile']['quality']['quality']['name'] : "unknown",
"audioChannels" => $child["hasFile"] && isset($child['episodeFile']['mediaInfo']) ? $child['episodeFile']['mediaInfo']['audioChannels'] : "unknown",
"audioCodec" => $child["hasFile"] && isset($child['episodeFile']['mediaInfo']) ? $child['episodeFile']['mediaInfo']['audioCodec'] : "unknown",
"videoCodec" => $child["hasFile"] && isset($child['episodeFile']['mediaInfo']) ? $child['episodeFile']['mediaInfo']['videoCodec'] : "unknown",
"size" => $child["hasFile"] && isset($child['episodeFile']['size']) ? $child['episodeFile']['size'] : "unknown",
"genres" => $child['series']['genres'],
"href" => strtolower($href),
"icon" => "/plugins/images/tabs/sonarr.png",
"frame" => $this->config['sonarrFrameTarget'],
"showLink" => $this->config['sonarrIcon']
];
$gotCalendar[] = [
"id" => "Sonarr-" . $number . "-" . $i,
"title" => $seriesName,
"start" => $child['airDateUtc'],
"className" => "inline-popups bg-calendar calendar-item tvID--" . $episodeID,
"imagetype" => "tv " . $downloaded,
"imagetypeFilter" => "tv",
"downloadFilter" => $downloaded,
"bgColor" => str_replace('text', 'bg', $downloaded),
"details" => $details
];
}
if ($i != 0) {
return $gotCalendar;
}
return false;
}
}

View File

@@ -0,0 +1,121 @@
<?php
trait SpeedTestHomepageItem
{
public function speedTestSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Speedtest',
'enabled' => true,
'image' => 'plugins/images/tabs/speedtest-icon.png',
'category' => 'Monitor',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('html', null, ['override' => 6, 'label' => 'Info', 'html' => '<p>This homepage item requires <a href="https://github.com/henrywhitaker3/Speedtest-Tracker" target="_blank" rel="noreferrer noopener">Speedtest-Tracker <i class="fa fa-external-link" aria-hidden="true"></i></a> to be running on your network.</p>']),
$this->settingsOption('enable', 'homepageSpeedtestEnabled'),
$this->settingsOption('auth', 'homepageSpeedtestAuth'),
],
'Connection' => [
$this->settingsOption('url', 'speedtestURL'),
$this->settingsOption('disable-cert-check', 'speedtestDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'speedtestUseCustomCertificate'),
],
'Options' => [
$this->settingsOption('title', 'speedtestHeader'),
$this->settingsOption('toggle-title', 'speedtestHeaderToggle'),
],
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function speedTestHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageSpeedtestEnabled'
],
'auth' => [
'homepageSpeedtestAuth'
],
'not_empty' => [
'speedtestURL'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderSpeedtest()
{
if ($this->homepageItemPermissions($this->speedTestHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Speedtest...</h2></div>
<script>
// Speedtest
homepageSpeedtest("' . $this->config['homepageSpeedtestRefresh'] . '");
// End Speedtest
</script>
</div>
';
}
}
public function getSpeedtestHomepageData()
{
if (!$this->homepageItemPermissions($this->speedTestHomepagePermissions('main'), true)) {
return false;
}
$api = [];
$url = $this->qualifyURL($this->config['speedtestURL']);
$options = $this->requestOptions($url, null, $this->config['speedtestDisableCertCheck'], $this->config['speedtestUseCustomCertificate']);
$dataUrl = $url . '/api/speedtest/latest';
try {
$response = Requests::get($dataUrl, [], $options);
if ($response->success) {
$json = json_decode($response->body, true);
$api['data'] = [
'current' => $json['data'],
];
$keys = [
'average',
'max',
'maximum',
'minimum'
];
foreach ($keys as $key) {
if (array_key_exists($key, $json)) {
if ($key == 'max') {
$api['data']['maximum'] = $json[$key];
} else {
$api['data'][$key] = $json[$key];
}
}
}
$api['options'] = [
'title' => $this->config['speedtestHeader'],
'titleToggle' => $this->config['speedtestHeaderToggle'],
];
} else {
$this->setAPIResponse('error', 'SpeedTest connection error', 409);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Speedtest')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
};
$api = isset($api) ? $api : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
}

View File

@@ -0,0 +1,292 @@
<?php
trait TautulliHomepageItem
{
public function tautulliSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Tautulli',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/tautulli.png',
'category' => 'Monitor',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$libraryList = [['name' => 'Refresh page to update List', 'value' => '', 'disabled' => true]];
if (!empty($this->config['tautulliApikey']) && !empty($this->config['tautulliURL'])) {
$libraryList = [];
$loop = $this->tautulliLibraryList();
if ($loop) {
$loop = $loop['libraries'];
foreach ($loop as $key => $value) {
$libraryList[] = ['name' => $key, 'value' => $value];
}
}
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageTautulliEnabled'),
$this->settingsOption('auth', 'homepageTautulliAuth'),
],
'Options' => [
$this->settingsOption('title', 'tautulliHeader'),
$this->settingsOption('toggle-title', 'tautulliHeaderToggle'),
$this->settingsOption('refresh', 'homepageTautulliRefresh'),
],
'Connection' => [
$this->settingsOption('multiple-url', 'tautulliURL'),
$this->settingsOption('multiple-api-key', 'tautulliApikey'),
$this->settingsOption('disable-cert-check', 'tautulliDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'tautulliUseCustomCertificate'),
],
'API SOCKS' => [
$this->settingsOption('socks', 'tautulli'),
$this->settingsOption('blank'),
$this->settingsOption('enable', 'tautulliSocksEnabled'),
$this->settingsOption('auth', 'tautulliSocksAuth'),
],
'Library Stats' => [
$this->settingsOption('switch', 'tautulliLibraries', ['label' => 'Libraries', 'help' => 'Shows/hides the card with library information.']),
$this->settingsOption('auth', 'homepageTautulliLibraryAuth'),
$this->settingsOption('plex-library-exclude', 'homepageTautulliLibraryStatsExclude', ['options' => $libraryList]),
],
'Viewing Stats' => [
$this->settingsOption('switch', 'tautulliPopularMovies', ['label' => 'Popular Movies', 'help' => 'Shows/hides the card with Popular Movie information.']),
$this->settingsOption('switch', 'tautulliPopularTV', ['label' => 'Popular TV', 'help' => 'Shows/hides the card with Popular TV information.']),
$this->settingsOption('switch', 'tautulliTopMovies', ['label' => 'Top Movies', 'help' => 'Shows/hides the card with Top Movies information.']),
$this->settingsOption('switch', 'tautulliTopTV', ['label' => 'Top TV', 'help' => 'Shows/hides the card with Top TV information.']),
$this->settingsOption('auth', 'homepageTautulliViewsAuth'),
$this->settingsOption('plex-library-exclude', 'homepageTautulliViewingStatsExclude', ['options' => $libraryList]),
],
'Misc Stats' => [
$this->settingsOption('switch', 'tautulliTopUsers', ['label' => 'Top Users', 'help' => 'Shows/hides the card with Top Users information.']),
$this->settingsOption('switch', 'tautulliTopPlatforms', ['label' => 'Top Platforms', 'help' => 'Shows/hides the card with Top Platforms information.']),
$this->settingsOption('auth', 'homepageTautulliMiscAuth'),
$this->settingsOption('switch', 'tautulliFriendlyName', ['label' => 'Use Friendly Name', 'help' => 'Use the friendly name set in tautulli for users.']),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'tautulli'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionTautulli()
{
$this->setLoggerChannel('Tautulli Homepage');
if (empty($this->config['tautulliURL'])) {
$this->setAPIResponse('error', 'Tautulli URL is not defined', 422);
return false;
}
if (empty($this->config['tautulliApikey'])) {
$this->setAPIResponse('error', 'Tautulli Token is not defined', 422);
return false;
}
$url = $this->qualifyURL($this->config['tautulliURL']);
$apiURL = $url . '/api/v2?apikey=' . $this->config['tautulliApikey'];
try {
$homestatsUrl = $apiURL . '&cmd=get_home_stats&grouping=1';
$options = $this->requestOptions($this->config['tautulliURL'], $this->config['homepageTautulliRefresh'], $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate'], ['follow_redirects' => false]);
$homestats = Requests::get($homestatsUrl, [], $options);
if ($homestats->success) {
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
} else {
$this->setAPIResponse('error', 'Tautulli Error Occurred - Check URL or Credentials', 409);
return false;
}
} catch (Requests_Exception $e) {
$this->logger->critical($e, [$url]);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function tautulliHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageTautulliEnabled'
],
'auth' => [
'homepageTautulliAuth'
],
'not_empty' => [
'tautulliURL',
'tautulliApikey'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrdertautulli()
{
if ($this->homepageItemPermissions($this->tautulliHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Tautulli...</h2></div>
<script>
// Tautulli
homepageTautulli("' . $this->config['homepageTautulliRefresh'] . '");
// End Tautulli
</script>
</div>
';
}
}
public function getTautulliHomepageData()
{
$this->setLoggerChannel('Tautulli Homepage');
if (!$this->homepageItemPermissions($this->tautulliHomepagePermissions('main'), true)) {
return false;
}
$api = [];
$url = $this->qualifyURL($this->config['tautulliURL']);
$apiURL = $url . '/api/v2?apikey=' . $this->config['tautulliApikey'];
$height = $this->getCacheImageSize('h');
$width = $this->getCacheImageSize('w');
$nowPlayingHeight = $this->getCacheImageSize('nph');
$nowPlayingWidth = $this->getCacheImageSize('npw');
$api['options'] = [
'url' => $url,
'libraries' => $this->config['tautulliLibraries'],
'topMovies' => $this->config['tautulliTopMovies'],
'topTV' => $this->config['tautulliTopTV'],
'topUsers' => $this->config['tautulliTopUsers'],
'topPlatforms' => $this->config['tautulliTopPlatforms'],
'popularMovies' => $this->config['tautulliPopularMovies'],
'popularTV' => $this->config['tautulliPopularTV'],
'title' => $this->config['tautulliHeaderToggle'],
'friendlyName' => $this->config['tautulliFriendlyName'],
];
try {
$homestatsUrl = $apiURL . '&cmd=get_home_stats&grouping=1';
$options = $this->requestOptions($this->config['tautulliURL'], $this->config['homepageTautulliRefresh'], $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate']);
$homestats = Requests::get($homestatsUrl, [], $options);
if ($homestats->success) {
$homepageTautulliViewingStatsExclude = explode(",", $this->config['homepageTautulliViewingStatsExclude']);
$homestats = json_decode($homestats->body, true);
foreach ($homestats['response']['data'] as $s => $stats) {
foreach ($stats['rows'] as $i => $v) {
if (array_key_exists('section_id', $v)) {
if (in_array($v['section_id'], $homepageTautulliViewingStatsExclude)) {
unset($homestats['response']['data'][$s]['rows'][$i]);
}
}
}
}
$homestats['response']['data'] = array_values($homestats['response']['data']);
$api['homestats'] = $homestats['response'];
// Cache art & thumb for first result in each tautulli API result
$categories = ['top_movies', 'top_tv', 'popular_movies', 'popular_tv'];
foreach ($categories as $cat) {
$key = array_search($cat, array_column($api['homestats']['data'], 'stat_id'));
if (count($api['homestats']['data'][$key]['rows']) > 0) {
$img = $api['homestats']['data'][$key]['rows'][0];
$this->cacheImage($url . '/pms_image_proxy?img=' . $img['art'] . '&rating_key=' . $img['rating_key'] . '&width=' . $nowPlayingWidth . '&height=' . $nowPlayingHeight, $img['rating_key'] . '-np');
$this->cacheImage($url . '/pms_image_proxy?img=' . $img['thumb'] . '&rating_key=' . $img['rating_key'] . '&width=' . $width . '&height=' . $height, $img['rating_key'] . '-list');
$img['art'] = 'data/cache/' . $img['rating_key'] . '-np.jpg';
$img['thumb'] = 'data/cache/' . $img['rating_key'] . '-list.jpg';
$api['homestats']['data'][$key]['rows'][0] = $img;
}
}
// Cache the platform icon
if (count($api['homestats']['data'][$key]['rows']) > 0) {
$key = array_search('top_platforms', array_column($api['homestats']['data'], 'stat_id'));
$platform = $api['homestats']['data'][$key]['rows'][0]['platform_name'];
$this->cacheImage($url . '/images/platforms/' . $platform . '.svg', 'tautulli-' . $platform, 'svg');
}
$libstatsUrl = $apiURL . '&cmd=get_libraries_table';
$options = $this->requestOptions($this->config['tautulliURL'], $this->config['homepageTautulliRefresh'], $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate']);
$libstats = Requests::get($libstatsUrl, [], $options);
if ($libstats->success) {
$homepageTautulliLibraryStatsExclude = explode(',', $this->config['homepageTautulliLibraryStatsExclude']);
$libstats = json_decode($libstats->body, true);
foreach ($libstats['response']['data']['data'] as $i => $v) {
if (array_key_exists('section_id', $v)) {
if (in_array($v['section_id'], $homepageTautulliLibraryStatsExclude)) {
unset($libstats['response']['data']['data'][$i]);
}
}
}
$libstats['response']['data']['data'] = array_values($libstats['response']['data']['data']);
$api['libstats'] = $libstats['response']['data'];
$categories = ['movie.svg', 'show.svg', 'artist.svg'];
foreach ($categories as $cat) {
$parts = explode('.', $cat);
$this->cacheImage($url . '/images/libraries/' . $cat, 'tautulli-' . $parts[0], $parts[1]);
}
}
$ids = []; // Array of stat_ids to remove from the returned array
if (!$this->qualifyRequest($this->config['homepageTautulliLibraryAuth'])) {
$api['options']['libraries'] = false;
unset($api['libstats']);
}
if (!$this->qualifyRequest($this->config['homepageTautulliViewsAuth'])) {
$api['options']['topMovies'] = false;
$api['options']['topTV'] = false;
$api['options']['popularMovies'] = false;
$api['options']['popularTV'] = false;
$ids = array_merge(['top_movies', 'popular_movies', 'popular_tv', 'top_tv'], $ids);
$api['homestats']['data'] = array_values($api['homestats']['data']);
}
if (!$this->qualifyRequest($this->config['homepageTautulliMiscAuth'])) {
$api['options']['topUsers'] = false;
$api['options']['topPlatforms'] = false;
$ids = array_merge(['top_platforms', 'top_users'], $ids);
$api['homestats']['data'] = array_values($api['homestats']['data']);
}
$ids = array_merge(['top_music', 'popular_music', 'last_watched', 'most_concurrent'], $ids);
foreach ($ids as $id) {
if ($key = array_search($id, array_column($api['homestats']['data'], 'stat_id'))) {
unset($api['homestats']['data'][$key]);
$api['homestats']['data'] = array_values($api['homestats']['data']);
}
}
}
} catch (Requests_Exception $e) {
$this->logger->critical($e, [$url]);
$this->setResponse(500, $e->getMessage());
return false;
}
$api = $api ?? false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
public function tautulliLibraryList()
{
$url = $this->qualifyURL($this->config['tautulliURL']);
$apiURL = $url . '/api/v2?apikey=' . $this->config['tautulliApikey'];
if (!empty($this->config['tautulliApikey']) && !empty($this->config['tautulliURL'])) {
$liblistUrl = $apiURL . '&cmd=get_libraries';
$options = $this->requestOptions($this->config['tautulliURL'], 10, $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate']);
try {
$liblist = Requests::get($liblistUrl, [], $options);
$libraryList = array();
if ($liblist->success) {
$liblist = json_decode($liblist->body, true);
foreach ($liblist['response']['data'] as $lib) {
$libraryList['libraries'][(string)$lib['section_name']] = (string)$lib["section_id"];
}
$libraryList = array_change_key_case($libraryList, CASE_LOWER);
return $libraryList;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Tautulli')->error($e);
return false;
}
}
return false;
}
}

View File

@@ -0,0 +1,276 @@
<?php
trait TraktHomepageItem
{
public function traktSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Trakt',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/trakt.png',
'category' => 'Calendar',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'docs' => 'https://docs.organizr.app/books/setup-features/page/trakt',
'debug' => true,
'settings' => [
'About' => [
$this->settingsOption('html', null, [
'override' => 12,
'html' => '
<div class="panel panel-default">
<div class="panel-wrapper collapse in">
<div class="panel-body">
<h3 lang="en">Trakt Homepage Item</h3>
<p lang="en">This homepage item enables the calendar on the homepage and displays your movies and/or tv shows from Trakt\'s API.</p>
<p lang="en">In order for this item to be setup, you need to goto the following URL to create a new API app.</p>
<p><a href="https://trakt.tv/oauth/applications/new" target="_blank">New API App</a></p>
<p lang="en">Enter anything for Name and Description. You can leave Javascript and Permissions blank. The only info you have to enter is for Redirect URI. Enter the following URL:</p>
<code class="elip hidden-xs">' . $this->getServerPath() . 'api/v2/oauth/trakt</code>
</div>
</div>
</div>'
]),
],
'Enable' => [
$this->settingsOption('enable', 'homepageTraktEnabled'),
$this->settingsOption('auth', 'homepageTraktAuth'),
],
'Connection' => [
$this->settingsOption('input', 'traktClientId', ['label' => 'Client Id']),
$this->settingsOption('password-alt', 'traktClientSecret', ['label' => 'Client Secret']),
$this->settingsOption('blank'),
$this->settingsOption('button', '', ['label' => 'Please Save before clicking button', 'icon' => 'fa fa-user', 'class' => 'pull-right', 'text' => 'Connect Account', 'attr' => 'onclick="openOAuth(\'trakt\')"']),
],
'Calendar' => [
$this->settingsOption('calendar-start', 'calendarStartTrakt', ['help' => 'Total Days (Adding start and end days) has a maximum of 33 Days from Trakt API']),
$this->settingsOption('calendar-end', 'calendarEndTrakt', ['help' => 'Total Days (Adding start and end days) has a maximum of 33 Days from Trakt API']),
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
$this->settingsOption('calendar-default-view', 'calendarDefault'),
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
$this->settingsOption('calendar-locale', 'calendarLocale'),
$this->settingsOption('calendar-limit', 'calendarLimit'),
$this->settingsOption('refresh', 'calendarRefresh'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function traktHomepagePermissions($key = null)
{
$permissions = [
'calendar' => [
'enabled' => [
'homepageTraktEnabled'
],
'auth' => [
'homepageTraktAuth'
],
'not_empty' => [
'traktClientId',
'traktAccessToken'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function getTraktCalendar($startDate = null)
{
$startDate = date('Y-m-d', strtotime('-' . $this->config['calendarStartTrakt'] . ' days'));
$calendarItems = array();
$errors = null;
$totalDays = (int)$this->config['calendarStartTrakt'] + (int)$this->config['calendarEndTrakt'];
if (!$this->homepageItemPermissions($this->traktHomepagePermissions('calendar'), true)) {
return false;
}
$headers = [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $this->config['traktAccessToken'],
'trakt-api-version' => 2,
'trakt-api-key' => $this->config['traktClientId']
];
$url = $this->qualifyURL('https://api.trakt.tv/calendars/my/shows/' . $startDate . '/' . $totalDays . '?extended=full');
$options = $this->requestOptions($url, $this->config['calendarRefresh']);
try {
$response = Requests::get($url, $headers, $options);
if ($response->success) {
$data = json_decode($response->body, true);
$traktTv = $this->formatTraktCalendarTv($data);
if (!empty($traktTv)) {
$calendarItems = array_merge($calendarItems, $traktTv);
}
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Trakt')->error($e);
$this->setResponse(500, $e->getMessage());
$errors = true;
}
$url = $this->qualifyURL('https://api.trakt.tv/calendars/my/movies/' . $startDate . '/' . $totalDays . '?extended=full');
try {
$response = Requests::get($url, $headers, $options);
if ($response->success) {
$data = json_decode($response->body, true);
$traktMovies = $this->formatTraktCalendarMovies($data);
if (!empty($traktTv)) {
$calendarItems = array_merge($calendarItems, $traktMovies);
}
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Trakt')->error($e);
$this->setResponse(500, $e->getMessage());
$errors = true;
}
if ($errors) {
$this->setAPIResponse('error', 'An error Occurred', 500, null);
return false;
}
$this->setAPIResponse('success', null, 200, $calendarItems);
$this->traktOAuthRefresh();
return $calendarItems;
}
public function formatTraktCalendarTv($array)
{
$gotCalendar = array();
$i = 0;
foreach ($array as $child) {
$i++;
$seriesName = $child['show']['title'];
$seriesID = $child['show']['ids']['tmdb'];
$episodeID = $child['show']['ids']['tmdb'];
if (!isset($episodeID)) {
$episodeID = "";
}
//$episodeName = htmlentities($child['title'], ENT_QUOTES);
$episodeAirDate = $child['first_aired'];
$episodeAirDate = strtotime($episodeAirDate);
$episodeAirDate = date("Y-m-d H:i:s", $episodeAirDate);
if (new DateTime() < new DateTime($episodeAirDate)) {
$unaired = true;
}
if ($child['episode']['number'] == 1) {
$episodePremier = "true";
} else {
$episodePremier = "false";
$date = new DateTime($episodeAirDate);
$date->add(new DateInterval("PT1S"));
$date->format(DateTime::ATOM);
$child['first_aired'] = gmdate('Y-m-d\TH:i:s\Z', strtotime($date->format(DateTime::ATOM)));
}
$downloaded = 0;
$monitored = 0;
if ($downloaded == "0" && isset($unaired) && $episodePremier == "true") {
$downloaded = "text-primary animated flash";
} elseif ($downloaded == "0" && isset($unaired) && $monitored == "0") {
$downloaded = "text-dark";
} elseif ($downloaded == "0" && isset($unaired)) {
$downloaded = "text-info";
} elseif ($downloaded == "1") {
$downloaded = "text-success";
} else {
$downloaded = "text-danger";
}
$fanart = "/plugins/images/homepage/no-np.png";
$bottomTitle = 'S' . sprintf("%02d", $child['episode']['season']) . 'E' . sprintf("%02d", $child['episode']['number']) . ' - ' . $child['episode']['title'];
$details = array(
"seasonCount" => $child['episode']['season'],
"status" => 'dunno',
"topTitle" => $seriesName,
"bottomTitle" => $bottomTitle,
"overview" => isset($child['episode']['overview']) ? $child['episode']['overview'] : '',
"runtime" => isset($child['episode']['runtime']) ? $child['episode']['runtime'] : '',
"image" => $fanart,
"ratings" => isset($child['show']['rating']) ? $child['show']['rating'] : '',
"videoQuality" => "unknown",
"audioChannels" => "unknown",
"audioCodec" => "unknown",
"videoCodec" => "unknown",
"size" => "unknown",
"genres" => isset($child['show']['genres']) ? $child['show']['genres'] : '',
);
array_push($gotCalendar, array(
"id" => "Trakt-Tv-" . $i,
"title" => $seriesName,
"start" => $child['first_aired'],
"className" => "inline-popups bg-calendar calendar-item get-tmdb-image tmdb-tv tmdbID--" . $seriesID,
"imagetype" => "tv " . $downloaded,
"imagetypeFilter" => "tv",
"downloadFilter" => $downloaded,
"bgColor" => str_replace('text', 'bg', $downloaded),
"details" => $details
));
}
if ($i != 0) {
return $gotCalendar;
}
return false;
}
public function formatTraktCalendarMovies($array)
{
$gotCalendar = array();
$i = 0;
foreach ($array as $child) {
$i++;
$movieName = $child['movie']['title'];
$movieID = $child['movie']['ids']['tmdb'];
if (!isset($movieID)) {
$movieID = '';
}
$physicalRelease = (isset($child['movie']['released']) ? $child['movie']['released'] : null);
//$backupRelease = (isset($child['info']['release_date']['theater']) ? $child['info']['release_date']['theater'] : null);
//$physicalRelease = (isset($physicalRelease) ? $physicalRelease : $backupRelease);
$physicalRelease = strtotime($physicalRelease);
$physicalRelease = date('Y-m-d', $physicalRelease);
$oldestDay = new DateTime ($this->currentTime);
$oldestDay->modify('-' . $this->config['calendarStart'] . ' days');
$newestDay = new DateTime ($this->currentTime);
$newestDay->modify('+' . $this->config['calendarEnd'] . ' days');
$startDt = new DateTime ($physicalRelease);
if (new DateTime() < $startDt) {
$notReleased = 'true';
} else {
$notReleased = 'false';
}
$downloaded = 'text-dark';
$banner = '/plugins/images/homepage/no-np.png';
$details = array(
'topTitle' => $movieName,
'bottomTitle' => $child['movie']['tagline'],
'status' => $child['movie']['status'],
'overview' => $child['movie']['overview'],
'runtime' => $child['movie']['runtime'],
'image' => $banner,
'ratings' => isset($child['movie']['rating']) ? $child['movie']['rating'] : '',
'videoQuality' => 'unknown',
'audioChannels' => '',
'audioCodec' => '',
'videoCodec' => '',
'genres' => $child['movie']['genres'],
'year' => isset($child['movie']['year']) ? $child['movie']['year'] : '',
'studio' => isset($child['movie']['year']) ? $child['movie']['year'] : '',
);
array_push($gotCalendar, array(
'id' => 'Trakt-Movie-' . $i,
'title' => $movieName,
'start' => $physicalRelease,
'className' => 'inline-popups bg-calendar calendar-item get-tmdb-image tmdb-movie tmdbID--' . $movieID,
'imagetype' => 'film ' . $downloaded,
'imagetypeFilter' => 'film',
'downloadFilter' => $downloaded,
'bgColor' => str_replace('text', 'bg', $downloaded),
'details' => $details
));
}
if ($i != 0) {
return $gotCalendar;
}
return [];
}
}

View File

@@ -0,0 +1,195 @@
<?php
trait TransmissionHomepageItem
{
public function transmissionSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Transmission',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/transmission.png',
'category' => 'Downloader',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageTransmissionEnabled'),
$this->settingsOption('auth', 'homepageTransmissionAuth'),
],
'Connection' => [
$this->settingsOption('url', 'transmissionURL', ['help' => 'Please do not included /web in URL. Please make sure to use local IP address and port - You also may use local dns name too.']),
$this->settingsOption('blank'),
$this->settingsOption('username', 'transmissionUsername'),
$this->settingsOption('password', 'transmissionPassword'),
$this->settingsOption('disable-cert-check', 'transmissionDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'transmissionUseCustomCertificate'),
],
'Misc Options' => [
$this->settingsOption('hide-seeding', 'transmissionHideSeeding'),
$this->settingsOption('hide-completed', 'transmissionHideCompleted'),
$this->settingsOption('refresh', 'transmissionRefresh'),
$this->settingsOption('combine', 'transmissionCombine'),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'transmission'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function testConnectionTransmission()
{
if (empty($this->config['transmissionURL'])) {
$this->setAPIResponse('error', 'Transmission URL is not defined', 422);
return false;
}
$digest = $this->qualifyURL($this->config['transmissionURL'], true);
$passwordInclude = ($this->config['transmissionUsername'] != '' && $this->config['transmissionPassword'] != '') ? $this->config['transmissionUsername'] . ':' . rawurlencode($this->decrypt($this->config['transmissionPassword'])) . "@" : '';
$url = $digest['scheme'] . '://' . $passwordInclude . $digest['host'] . $digest['port'] . $digest['path'] . '/rpc';
try {
$options = $this->requestOptions($this->config['transmissionURL'], $this->config['transmissionRefresh'], $this->config['transmissionDisableCertCheck'], $this->config['transmissionUseCustomCertificate']);
$response = Requests::get($url, [], $options);
if ($response->headers['x-transmission-session-id']) {
$headers = array(
'X-Transmission-Session-Id' => $response->headers['x-transmission-session-id'],
'Content-Type' => 'application/json'
);
$data = array(
'method' => 'torrent-get',
'arguments' => array(
'fields' => array(
"id", "name", "totalSize", "eta", "isFinished", "isStalled", "percentDone", "rateDownload", "status", "downloadDir", "errorString"
),
),
'tags' => ''
);
$response = Requests::post($url, $headers, json_encode($data), $options);
if ($response->success) {
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
} else {
$this->setAPIResponse('error', 'Transmission Connect Function - Error: Unknown', 500);
return false;
}
} else {
$this->setLoggerChannel('Transmission')->warning('Could not get session ID');
$this->setAPIResponse('error', 'Transmission Connect Function - Error: Could not get session ID', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Transmission')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function transmissionHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageTransmissionEnabled'
],
'auth' => [
'homepageTransmissionAuth'
],
'not_empty' => [
'transmissionURL'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrdertransmission()
{
if ($this->homepageItemPermissions($this->transmissionHomepagePermissions('main'))) {
$loadingBox = ($this->config['transmissionCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
$builder = ($this->config['transmissionCombine']) ? 'buildDownloaderCombined(\'transmission\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("transmission"));';
return '
<div id="' . __FUNCTION__ . '">
' . $loadingBox . '
<script>
// homepageOrdertransmission
' . $builder . '
homepageDownloader("transmission", "' . $this->config['transmissionRefresh'] . '");
// End homepageOrdertransmission
</script>
</div>
';
}
}
public function getTransmissionHomepageQueue()
{
if (!$this->homepageItemPermissions($this->transmissionHomepagePermissions('main'), true)) {
return false;
}
$digest = $this->qualifyURL($this->config['transmissionURL'], true);
$passwordInclude = ($this->config['transmissionUsername'] != '' && $this->config['transmissionPassword'] != '') ? $this->config['transmissionUsername'] . ':' . rawurlencode($this->decrypt($this->config['transmissionPassword'])) . "@" : '';
$url = $digest['scheme'] . '://' . $passwordInclude . $digest['host'] . $digest['port'] . $digest['path'] . '/rpc';
try {
$options = $this->requestOptions($this->config['transmissionURL'], $this->config['transmissionRefresh'], $this->config['transmissionDisableCertCheck'], $this->config['transmissionUseCustomCertificate']);
$response = Requests::get($url, array(), $options);
if ($response->headers['x-transmission-session-id']) {
$headers = array(
'X-Transmission-Session-Id' => $response->headers['x-transmission-session-id'],
'Content-Type' => 'application/json'
);
$data = array(
'method' => 'torrent-get',
'arguments' => array(
'fields' => array(
"id", "name", "totalSize", "eta", "isFinished", "isStalled", "percentDone", "rateDownload", "status", "downloadDir", "errorString", "addedDate"
),
),
'tags' => ''
);
$response = Requests::post($url, $headers, json_encode($data), $options);
if ($response->success) {
$torrentList = json_decode($response->body, true)['arguments']['torrents'];
if ($this->config['transmissionHideSeeding'] || $this->config['transmissionHideCompleted']) {
$filter = array();
$torrents = array();
if ($this->config['transmissionHideSeeding']) {
array_push($filter, 6, 5);
}
if ($this->config['transmissionHideCompleted']) {
array_push($filter, 0);
}
foreach ($torrentList as $key => $value) {
if (!in_array($value['status'], $filter)) {
$torrents[] = $value;
}
}
} else {
$torrents = json_decode($response->body, true)['arguments']['torrents'];
}
usort($torrents, function ($a, $b) {
return $a["addedDate"] <=> $b["addedDate"];
});
$api['content']['queueItems'] = $torrents;
$api['content']['historyItems'] = false;
}
} else {
$this->setLoggerChannel('Transmission')->warning('Could not get session ID');
$this->setAPIResponse('error', 'Transmission Connect Function - Error: Could not get session ID', 500);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Transmission')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = $api['content'] ?? false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
}

View File

@@ -0,0 +1,240 @@
<?php
trait UnifiHomepageItem
{
public function unifiSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'UniFi',
'enabled' => true,
'image' => 'plugins/images/tabs/unifi.png',
'category' => 'Monitor',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageUnifiEnabled'),
$this->settingsOption('auth', 'homepageUnifiAuth'),
],
'Connection' => [
$this->settingsOption('url', 'unifiURL'),
$this->settingsOption('blank'),
$this->settingsOption('disable-cert-check', 'unifiDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'unifiUseCustomCertificate'),
$this->settingsOption('username', 'unifiUsername', ['help' => 'Username is case-sensitive']),
$this->settingsOption('password', 'unifiPassword'),
$this->settingsOption('input', 'unifiSiteName', ['label' => 'Site Name (Not for UnifiOS)', 'help' => 'Site Name - not Site ID nor Site Description']),
$this->settingsOption('button', '', ['label' => 'Grab Unifi Site (Not for UnifiOS)', 'icon' => 'fa fa-building', 'text' => 'Get Unifi Site', 'attr' => 'onclick="getUnifiSite()"']),
],
'Misc Options' => [
$this->settingsOption('refresh', 'homepageUnifiRefresh'),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'unifi'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function unifiHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageUnifiEnabled'
],
'auth' => [
'homepageUnifiAuth'
],
'not_empty' => [
'unifiURL',
'unifiUsername',
'unifiPassword'
]
],
'test' => [
'auth' => [
'homepageUnifiAuth'
],
'not_empty' => [
'unifiURL',
'unifiUsername',
'unifiPassword'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderunifi()
{
if ($this->homepageItemPermissions($this->unifiHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Unifi...</h2></div>
<script>
// Unifi
homepageUnifi("' . $this->config['homepageHealthChecksRefresh'] . '");
// End Unifi
</script>
</div>
';
}
}
public function getUnifiSiteName()
{
if (!$this->homepageItemPermissions($this->unifiHomepagePermissions('test'), true)) {
return false;
}
try {
$login = $this->unifiLogin();
if ($login) {
$url = $this->qualifyURL($this->config['unifiURL']);
$unifiOS = $login['unifiOS'];
if ($unifiOS) {
$this->setResponse(500, 'Unifi OS does not support Multi Site');
return false;
}
$response = Requests::get($url . '/api/self/sites', [], $login['options']);
if ($response->success) {
$body = json_decode($response->body, true);
$this->setAPIResponse('success', null, 200, $body);
return $body;
} else {
$this->setAPIResponse('error', 'Unifi response error3', 409);
return false;
}
} else {
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Unifi')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function isUnifiOS()
{
try {
// Is this UnifiOs or Regular
$url = $this->qualifyURL($this->config['unifiURL']);
$options = $this->requestOptions($url, $this->config['homepageUnifiRefresh'], $this->config['unifiDisableCertCheck'], $this->config['unifiUseCustomCertificate'], ['follow_redirects' => true]);
$response = Requests::get($url, [], $options);
if ($response->success) {
return ($response->headers['x-csrf-token']) ?? false;
} else {
$this->setAPIResponse('error', 'Unifi response error - Check URL', 409);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Unifi')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function unifiLogin()
{
$csrfToken = $this->isUnifiOS();
$url = $this->qualifyURL($this->config['unifiURL']);
$options = $this->requestOptions($url, $this->config['homepageUnifiRefresh'], $this->config['unifiDisableCertCheck'], $this->config['unifiUseCustomCertificate'], ['follow_redirects' => true]);
$data = array(
'username' => $this->config['unifiUsername'],
'password' => $this->decrypt($this->config['unifiPassword']),
'remember' => true,
'strict' => true
);
try {
$data = ($csrfToken) ? $data : json_encode($data);
$headers = ($csrfToken) ? ['x-csrf-token' => $csrfToken] : [];
$urlLogin = ($csrfToken) ? $url . '/api/auth/login' : $url . '/api/login';
$response = Requests::post($urlLogin, $headers, $data, $options);
if ($response->success) {
$options['cookies'] = $response->cookies;
return [
'unifiOS' => $csrfToken,
'options' => $options
];
} else {
$this->setAPIResponse('error', 'Unifi response error - Check Credentials', 409);
return false;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Unifi')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function testConnectionUnifi()
{
if (!$this->homepageItemPermissions($this->unifiHomepagePermissions('test'), true)) {
return false;
}
try {
// Is this UnifiOs or Regular
$api['content']['unifi'] = array();
$login = $this->unifiLogin();
if ($login) {
$url = $this->qualifyURL($this->config['unifiURL']);
$unifiOS = $login['unifiOS'];
$headers = ($unifiOS) ? ['x-csrf-token' => $unifiOS] : [];
$urlStat = ($unifiOS) ? $url . '/proxy/network/api/s/default/stat/health' : $url . '/api/s/' . $this->config['unifiSiteName'] . '/stat/health';
$response = Requests::get($urlStat, $headers, $login['options']);
if ($response->success) {
$api['content']['unifi'] = json_decode($response->body, true);
} else {
$this->setAPIResponse('error', 'Unifi response error3', 409);
return false;
}
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Unifi')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
$api['content']['unifi'] = $api['content']['unifi'] ?? false;
$this->setAPIResponse('success', 'API Connection succeeded', 200);
return true;
}
public function getUnifiHomepageData()
{
if (!$this->homepageItemPermissions($this->unifiHomepagePermissions('main'), true)) {
return false;
}
try {
$api['content']['unifi'] = array();
$login = $this->unifiLogin();
if ($login) {
$url = $this->qualifyURL($this->config['unifiURL']);
$unifiOS = $login['unifiOS'];
$headers = ($unifiOS) ? ['x-csrf-token' => $unifiOS] : [];
$urlStat = ($unifiOS) ? $url . '/proxy/network/api/s/default/stat/health' : $url . '/api/s/' . $this->config['unifiSiteName'] . '/stat/health';
$response = Requests::get($urlStat, $headers, $login['options']);
if ($response->success) {
$api['content']['unifi'] = json_decode($response->body, true);
} else {
$this->setAPIResponse('error', 'Unifi response error3', 409);
return false;
}
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Unifi')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
$api['content']['unifi'] = $api['content']['unifi'] ?? false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
}

View File

@@ -0,0 +1,127 @@
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
trait UptimeKumaHomepageItem
{
private static Client $kumaClient;
public function uptimeKumaSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'UptimeKuma',
'enabled' => true,
'image' => 'plugins/images/tabs/kuma.png',
'category' => 'Monitor',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageUptimeKumaEnabled'),
$this->settingsOption('auth', 'homepageUptimeKumaAuth'),
],
'Connection' => [
$this->settingsOption('url', 'uptimeKumaURL', ['help' => 'URL for Uptime Kuma e.g. http://kuma:3001 (no trailing slash)', 'placeholder' => 'http://kuma:3001']),
$this->settingsOption('token', 'uptimeKumaToken'),
],
'Options' => [
$this->settingsOption('refresh', 'homepageUptimeKumaRefresh'),
$this->settingsOption('title', 'homepageUptimeKumaHeader'),
$this->settingsOption('toggle-title', 'homepageUptimeKumaHeaderToggle'),
$this->settingsOption('switch', 'homepageUptimeKumaCompact', ['label' => 'Compact view', 'help' => 'Toggles the compact view of this homepage module']),
$this->settingsOption('switch', 'homepageUptimeKumaShowLatency', ['label' => 'Show monitor latency']),
],
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function uptimeKumaHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageUptimeKumaEnabled'
],
'auth' => [
'homepageUptimeKumaAuth'
],
'not_empty' => [
'uptimeKumaURL',
'uptimeKumaToken',
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderUptimeKuma()
{
if ($this->homepageItemPermissions($this->uptimeKumaHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Uptime Kuma...</h2></div>
<script>
// Uptime Kuma
homepageUptimeKuma("' . $this->config['homepageUptimeKumaRefresh'] . '");
// End Uptime Kuma
</script>
</div>
';
}
}
public function getUptimeKumaHomepageData()
{
if (!$this->homepageItemPermissions($this->uptimeKumaHomepagePermissions('main'), true)) {
return false;
}
$api = [];
$url = $this->qualifyURL($this->config['uptimeKumaURL']);
try {
$metrics = (new UptimeKumaMetrics(
$this->getKumaClient($url, $this->config['uptimeKumaToken'])
->get('/metrics')
->getBody()
->getContents()
))->process();
$api = [
'data' => $metrics->getMonitors(),
'options' => [
'title' => $this->config['homepageUptimeKumaHeader'],
'titleToggle' => $this->config['homepageUptimeKumaHeaderToggle'],
'compact' => $this->config['homepageUptimeKumaCompact'],
'showLatency' => $this->config['homepageUptimeKumaShowLatency'],
]
];
} catch (GuzzleException $e) {
$this->setLoggerChannel('UptimeKuma')->error($e);
$this->setAPIResponse('error', $e->getMessage(), 401);
return false;
};
$api = isset($api) ? $api : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
private function getKumaClient(string $url, string $token): Client
{
if (!isset(static::$kumaClient)) {
static::$kumaClient = new Client([
'base_uri' => $url,
'headers' => [
'Authorization' => sprintf("Basic %s", base64_encode(':'.$token)),
],
]);
}
return static::$kumaClient;
}
}

View File

@@ -0,0 +1,233 @@
<?php
trait uTorrentHomepageItem
{
public function uTorrentSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'uTorrent',
'enabled' => strpos('personal', $this->config['license']) !== false,
'image' => 'plugins/images/tabs/utorrent.png',
'category' => 'Downloader',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageuTorrentEnabled'),
$this->settingsOption('auth', 'homepageuTorrentAuth'),
],
'Connection' => [
$this->settingsOption('url', 'uTorrentURL'),
$this->settingsOption('blank'),
$this->settingsOption('username', 'uTorrentUsername'),
$this->settingsOption('password', 'uTorrentPassword'),
$this->settingsOption('disable-cert-check', 'uTorrentDisableCertCheck'),
$this->settingsOption('use-custom-certificate', 'uTorrentUseCustomCertificate'),
],
'Misc Options' => [
$this->settingsOption('hide-seeding', 'uTorrentHideSeeding', ['label' => 'Hide Seeding']),
$this->settingsOption('hide-completed', 'uTorrentHideCompleted'),
$this->settingsOption('refresh', 'uTorrentRefresh'),
$this->settingsOption('combine', 'uTorrentCombine'),
],
'Test Connection' => [
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
$this->settingsOption('test', 'utorrent'),
]
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function uTorrentHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageuTorrentEnabled'
],
'auth' => [
'homepageuTorrentAuth'
],
'not_empty' => [
'uTorrentURL'
]
]
];
if (array_key_exists($key, $permissions)) {
return $permissions[$key];
} elseif ($key == 'all') {
return $permissions;
} else {
return [];
}
}
public function testConnectionuTorrent()
{
if (empty($this->config['uTorrentURL'])) {
$this->setAPIResponse('error', 'uTorrent URL is not defined', 422);
return false;
}
try {
$response = $this->getuTorrentToken();
} catch (Requests_Exception $e) {
$this->setLoggerChannel('uTorrent')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function homepageOrderuTorrent()
{
if ($this->homepageItemPermissions($this->uTorrentHomepagePermissions('main'))) {
$loadingBox = ($this->config['uTorrentCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
$builder = ($this->config['uTorrentCombine']) ? 'buildDownloaderCombined(\'utorrent\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("utorrent"));';
return '
<div id="' . __FUNCTION__ . '">
' . $loadingBox . '
<script>
// homepageOrderuTorrent
' . $builder . '
homepageDownloader("utorrent", "' . $this->config['uTorrentRefresh'] . '");
// End homepageOrderuTorrent
</script>
</div>
';
}
}
public function getuTorrentToken()
{
try {
$tokenUrl = '/gui/token.html';
$digest = $this->qualifyURL($this->config['uTorrentURL'], true);
$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $tokenUrl;
$data = array('username' => $this->config['uTorrentUsername'], 'password' => $this->decrypt($this->config['uTorrentPassword']));
$options = $this->requestOptions($url, null, $this->config['uTorrentDisableCertCheck'], $this->config['uTorrentUseCustomCertificate']);
if ($this->config['uTorrentUsername'] !== '' && $this->decrypt($this->config['uTorrentPassword']) !== '') {
$credentials = array('auth' => new Requests_Auth_Basic(array($this->config['uTorrentUsername'], $this->decrypt($this->config['uTorrentPassword']))));
$options = array_merge($options, $credentials);
}
$response = Requests::post($url, [], $data, $options);
$dom = new PHPHtmlParser\Dom;
$dom->loadStr($response->body);
$id = $dom->getElementById('token')->text;
$uTorrentConfig = array(
"uTorrentToken" => $id,
"uTorrentCookie" => "",
);
$reflection = new ReflectionClass($response->cookies);
$cookie = $reflection->getProperty("cookies");
$cookie->setAccessible(true);
$cookie = $cookie->getValue($response->cookies);
if ($cookie['GUID']) {
$uTorrentConfig['uTorrentCookie'] = $cookie['GUID']->value;
}
if ($uTorrentConfig['uTorrentToken'] || $uTorrentConfig['uTorrentCookie']) {
$this->updateConfigItems($uTorrentConfig);
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('uTorrent')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
public function getuTorrentHomepageQueue()
{
if (!$this->homepageItemPermissions($this->uTorrentHomepagePermissions('main'), true)) {
return false;
}
try {
if (!$this->config['uTorrentToken'] || !$this->config['uTorrentCookie']) {
$this->getuTorrentToken();
}
$queryUrl = '/gui/?token=' . $this->config['uTorrentToken'] . '&list=1';
$digest = $this->qualifyURL($this->config['uTorrentURL'], true);
$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $queryUrl;
$options = $this->requestOptions($url, null, $this->config['uTorrentDisableCertCheck'], $this->config['uTorrentUseCustomCertificate']);
if ($this->config['uTorrentUsername'] !== '' && $this->decrypt($this->config['uTorrentPassword']) !== '') {
$credentials = array('auth' => new Requests_Auth_Basic(array($this->config['uTorrentUsername'], $this->decrypt($this->config['uTorrentPassword']))));
$options = array_merge($options, $credentials);
}
$headers = array(
'Cookie' => 'GUID=' . $this->config['uTorrentCookie']
);
$response = Requests::get($url, $headers, $options);
$httpResponse = $response->status_code;
if ($httpResponse == 400) {
$this->setLoggerChannel('uTorrent')->warning('Token or Cookie Expired. Generating new session...');
$this->getuTorrentToken();
$response = Requests::get($url, $headers, $options);
$httpResponse = $response->status_code;
}
if ($httpResponse == 200) {
$responseData = json_decode($response->body);
$keyArray = (array)$responseData->torrents;
//Populate values
$valueArray = array();
foreach ($keyArray as $keyArr) {
preg_match('/(?<Status>(\w+\s+)+)(?<Percentage>\d+.\d+.*)/', $keyArr[21], $matches);
$Status = str_replace(' ', '', $matches['Status']);
if ($this->config['uTorrentHideSeeding'] && $Status == "Seeding") {
// Do Nothing
} else if ($this->config['uTorrentHideCompleted'] && $Status == "Finished") {
// Do Nothing
} else {
$value = array(
'Hash' => $keyArr[0],
'TorrentStatus' => $keyArr[1],
'Name' => $keyArr[2],
'Size' => $keyArr[3],
'Progress' => $keyArr[4],
'Downloaded' => $keyArr[5],
'Uploaded' => $keyArr[6],
'Ratio' => $keyArr[7],
'upSpeed' => $keyArr[8],
'downSpeed' => $keyArr[9],
'eta' => $keyArr[10],
'Labels' => $keyArr[11],
'PeersConnected' => $keyArr[12],
'PeersInSwarm' => $keyArr[13],
'SeedsConnected' => $keyArr[14],
'SeedsInSwarm' => $keyArr[15],
'Availability' => $keyArr[16],
'TorrentQueueOrder' => $keyArr[17],
'Remaining' => $keyArr[18],
'DownloadUrl' => $keyArr[19],
'RssFeedUrl' => $keyArr[20],
'Message' => $keyArr[21],
'StreamId' => $keyArr[22],
'DateAdded' => $keyArr[23],
'DateCompleted' => $keyArr[24],
'AppUpdateUrl' => $keyArr[25],
'RootDownloadPath' => $keyArr[26],
'Unknown27' => $keyArr[27],
'Unknown28' => $keyArr[28],
'Status' => $Status,
'Percent' => str_replace(' ', '', $matches['Percentage']),
);
array_push($valueArray, $value);
}
}
$api['content']['queueItems'] = $valueArray;
$api['content'] = $api['content'] ?? false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('uTorrent')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
}
}
}

View File

@@ -0,0 +1,149 @@
<?php
trait WeatherHomepageItem
{
public function weatherSettingsArray($infoOnly = false)
{
$homepageInformation = [
'name' => 'Weather-Air',
'enabled' => true,
'image' => 'plugins/images/tabs/wind.png',
'category' => 'Monitor',
'settingsArray' => __FUNCTION__
];
if ($infoOnly) {
return $homepageInformation;
}
$homepageSettings = [
'debug' => true,
'settings' => [
'Enable' => [
$this->settingsOption('enable', 'homepageWeatherAndAirEnabled'),
$this->settingsOption('auth', 'homepageWeatherAndAirAuth'),
],
'Connection' => [
$this->settingsOption('input', 'homepageWeatherAndAirLatitude', ['label' => 'Latitude', 'help' => 'Please enter full latitude including minus if needed']),
$this->settingsOption('input', 'homepageWeatherAndAirLongitude', ['label' => 'Longitude', 'help' => 'Please enter full longitude including minus if needed']),
$this->settingsOption('blank'),
$this->settingsOption('button', null, ['type' => 'button', 'label' => '', 'icon' => 'fa fa-search', 'class' => 'pull-right', 'text' => 'Need Help With Coordinates?', 'attr' => 'onclick="showLookupCoordinatesModal()"']),
],
'Options' => [
$this->settingsOption('title', 'homepageWeatherAndAirWeatherHeader'),
$this->settingsOption('toggle-title', 'homepageWeatherAndAirWeatherHeaderToggle'),
$this->settingsOption('enable', 'homepageWeatherAndAirWeatherEnabled', ['label' => 'Enable Weather', 'help' => 'Toggles the view module for Weather']),
$this->settingsOption('enable', 'homepageWeatherAndAirAirQualityEnabled', ['label' => 'Enable Air Quality', 'help' => 'Toggles the view module for Air Quality']),
$this->settingsOption('enable', 'homepageWeatherAndAirPollenEnabled', ['label' => 'Enable Pollen', 'help' => 'Toggles the view module for Pollen']),
$this->settingsOption('select', 'homepageWeatherAndAirUnits', ['label' => 'Unit of Measurement', 'options' => [['name' => 'Imperial', 'value' => 'imperial'], ['name' => 'Metric', 'value' => 'metric']]]),
$this->settingsOption('refresh', 'homepageWeatherAndAirRefresh'),
],
]
];
return array_merge($homepageInformation, $homepageSettings);
}
public function weatherHomepagePermissions($key = null)
{
$permissions = [
'main' => [
'enabled' => [
'homepageWeatherAndAirEnabled'
],
'auth' => [
'homepageWeatherAndAirAuth'
],
'not_empty' => [
'homepageWeatherAndAirLatitude',
'homepageWeatherAndAirLongitude'
]
]
];
return $this->homepageCheckKeyPermissions($key, $permissions);
}
public function homepageOrderWeatherAndAir()
{
if ($this->homepageItemPermissions($this->weatherHomepagePermissions('main'))) {
return '
<div id="' . __FUNCTION__ . '">
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Weather...</h2></div>
<script>
// Weather And Air
homepageWeatherAndAir("' . $this->config['homepageWeatherAndAirRefresh'] . '");
// End Weather And Air
</script>
</div>
';
}
}
public function searchCityForCoordinates($query)
{
try {
$query = $query ?? false;
if (!$query) {
$this->setAPIResponse('error', 'Query was not supplied', 422);
return false;
}
$url = $this->qualifyURL('https://api.mapbox.com/geocoding/v5/mapbox.places/' . urlencode($query) . '.json?access_token=pk.eyJ1IjoiY2F1c2VmeCIsImEiOiJjazhyeGxqeXgwMWd2M2ZydWQ4YmdjdGlzIn0.R50iYuMewh1CnUZ7sFPdHA&limit=5&fuzzyMatch=true');
$response = Requests::get($url);
if ($response->success) {
$this->setAPIResponse('success', null, 200, json_decode($response->body));
return json_decode($response->body);
}
} catch (Requests_Exception $e) {
$this->setResponse(500, $e->getMessage());
return false;
};
}
public function getWeatherAndAirData()
{
if (!$this->homepageItemPermissions($this->weatherHomepagePermissions('main'), true)) {
return false;
}
$api['content'] = array(
'weather' => false,
'air' => false,
'pollen' => false
);
$apiURL = $this->qualifyURL('https://api.breezometer.com/');
$info = '&lat=' . $this->config['homepageWeatherAndAirLatitude'] . '&lon=' . $this->config['homepageWeatherAndAirLongitude'] . '&units=' . $this->config['homepageWeatherAndAirUnits'] . '&key=' . $this->config['breezometerToken'];
try {
if ($this->config['homepageWeatherAndAirWeatherEnabled']) {
$endpoint = '/weather/v1/forecast/hourly?hours=120&metadata=true';
$options = $this->requestOptions($apiURL, $this->config['homepageWeatherAndAirRefresh']);
$response = Requests::get($apiURL . $endpoint . $info, [], $options);
if ($response->success) {
$apiData = json_decode($response->body, true);
$api['content']['weather'] = ($apiData['error'] === null) ? $apiData : false;
unset($apiData);
}
}
if ($this->config['homepageWeatherAndAirAirQualityEnabled']) {
$endpoint = '/air-quality/v2/current-conditions?features=breezometer_aqi,local_aqi,health_recommendations,sources_and_effects,dominant_pollutant_concentrations,pollutants_concentrations,pollutants_aqi_information&metadata=true';
$response = Requests::get($apiURL . $endpoint . $info);
if ($response->success) {
$apiData = json_decode($response->body, true);
$api['content']['air'] = ($apiData['error'] === null) ? $apiData : false;
unset($apiData);
}
}
if ($this->config['homepageWeatherAndAirPollenEnabled']) {
$endpoint = '/pollen/v2/forecast/daily?features=plants_information,types_information&days=1&metadata=true';
$response = Requests::get($apiURL . $endpoint . $info);
if ($response->success) {
$apiData = json_decode($response->body, true);
$api['content']['pollen'] = ($apiData['error'] === null) ? $apiData : false;
unset($apiData);
}
}
} catch (Requests_Exception $e) {
$this->setLoggerChannel('Weather & Air')->error($e);
$this->setResponse(500, $e->getMessage());
return false;
};
$api['content'] = isset($api['content']) ? $api['content'] : false;
$this->setAPIResponse('success', null, 200, $api);
return $api;
}
}

View File

@@ -0,0 +1,22 @@
<?php
reset($_GET);
$function = (key($_GET) ? str_replace("/", "_", key($_GET)) : false);
function validateData($data)
{
$data = trim($data);
$data = stripslashes($data);
return htmlspecialchars($data);
}
switch ($function) {
case 'v1_auth':
$group = ($_GET['group']) ?? 0;
header('Location: v2/auth?group=' . validateData($group));
exit;
default:
// Forward everything to v2 api
$result['status'] = "error";
$result['statusText'] = "Please Use api/v2";
break;
}
header('Location: v2/');

View File

@@ -0,0 +1,126 @@
<?php
$GLOBALS['organizrPages'][] = 'dependencies';
function get_page_dependencies($Organizr)
{
if (!$Organizr) {
$Organizr = new Organizr();
}
return '
<script>
</script>
<div class="container-fluid">
<div class="row bg-title">
<div class="col-lg-3 col-md-4 col-sm-4 col-xs-12">
<h4 class="page-title" lang="en">Organizr Dependency Check</h4>
</div>
<!-- /.col-lg-12 -->
</div>
<!--.row-->
<div class="row">
<div class="col-sm-12">
<div class="white-box">
<div class="row row-in">
<div class="col-lg-4 col-sm-6 row-in-br">
<ul class="col-in">
<li>
<span class="circle circle-md bg-warning dependency-dependencies-check"><i class="fa fa-spin fa-spinner"></i></span>
</li>
<li class="col-last">
<h3 class="counter text-right m-t-15" lang="en">Dependencies</h3>
</li>
</ul>
</div>
<div class="col-lg-4 col-sm-6 row-in-br b-r-none">
<ul class="col-in">
<li>
<span class="circle circle-md bg-warning dependency-phpversion-check"><i class="fa fa-spin fa-spinner"></i></span>
</li>
<li class="col-last">
<h3 class="counter text-right m-t-15" lang="en">PHP Version</h3>
</li>
</ul>
</div>
<div class="col-lg-4 col-sm-6 b-0">
<ul class="col-in">
<li>
<span class="circle circle-md bg-warning dependency-permissions-check"><i class="fa fa-spin fa-spinner"></i></span>
</li>
<li class="col-last">
<h3 class="counter text-right m-t-15" lang="en">Permissions</h3>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-4 col-sm-6">
<div class="panel panel-danger dependency-dependencies-check-listing-header">
<div class="panel-heading dependency-dependencies-check-listing"> <i class="ti-alert fa-fw"></i> <span lang="en">Dependencies Missing</span>
<div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a></div>
</div>
<div class="panel-wrapper collapse in" aria-expanded="true">
<div class="panel-body">
<ul class="common-list" id="depenency-info"></ul>
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-sm-6">
<div class="panel panel-info">
<div class="panel-heading"> <i class="ti-alert fa-fw"></i> <span lang="en">PHP Version Check</span>
<div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a></div>
</div>
<div class="panel-wrapper collapse in" aria-expanded="true">
<table class="table table-hover">
<tbody>
<tr>
<td id="php-version-check" lang="en">Loading...</td>
</tr>
<tr>
<td id="php-version-check-user" lang="en">Loading...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-lg-4 col-sm-6">
<div class="panel panel-info">
<div class="panel-heading"> <i class="ti-alert fa-fw"></i> <span lang="en">Web Folder</span>
<div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a></div>
</div>
<div class="panel-wrapper collapse in" aria-expanded="true">
<table class="table table-hover">
<tbody>
<tr>
<td>' . dirname(__DIR__, 2) . '</td>
</tr>
<tr>
<td id="web-folder" lang="en">Loading...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="panel panel-info">
<div class="panel-heading"> <i class="ti-alert fa-fw"></i> <span lang="en">Browser Information</span>
<div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-plus"></i></a></div>
</div>
<div class="panel-wrapper collapse" id="browser-info" aria-expanded="false"></div>
</div>
</div>
</div>
<!--./row-->
</div>
<!-- /.container-fluid -->
';
}

View File

@@ -0,0 +1,149 @@
<?php
$GLOBALS['organizrPages'][] = 'error';
function get_page_error($Organizr)
{
if (!$Organizr) {
$Organizr = new Organizr();
}
if ((!$Organizr->hasDB())) {
return false;
}
$nonRoot = isset($_GET['organizr']);
$nonRootPath = ($nonRoot) ? $Organizr->getRootPath() : '';
$error = $_GET['vars']['var1'] ?? 404;
$errorDetails = $Organizr->errorCodes($error);
$redirect = $_GET['vars']['var2'] ?? null;
if ($redirect) {
$Organizr->logger->debug($redirect);
}
$GLOBALS['responseCode'] = 200;
return '
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta content="' . $Organizr->config['description'] . '" name="description">
<meta content="CauseFX" name="author">
' . $Organizr->favIcons($nonRootPath) . '
<title>Error ' . $Organizr->config['title'] . '</title>
' . $Organizr->loadResources(
[
'bootstrap/dist/css/bootstrap.min.css',
'css/animate.css',
'plugins/bower_components/overlayScrollbars/OverlayScrollbars.min.css',
'css/dark.min.css',
'css/organizr.min.css',
'js/jquery-2.2.4.min.js',
'js/jquery-lang.min.js'
], $nonRootPath
) . '
' . $Organizr->setTheme(null, $nonRootPath) . '
<style id="user-appearance"></style>
<style id="custom-theme-css"></style>
<style id="custom-css"></style>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"
integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY"
crossorigin="anonymous"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"
integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo"
crossorigin="anonymous"></script>
<![endif]-->
</head>
<body class="fix-header">
<!-- ============================================================== -->
<!-- Preloader -->
<!-- ==============================================================
<div id="preloader" class="preloader">
<svg class="circular" viewbox="25 25 50 50">
<circle class="path" cx="50" cy="50" fill="none" r="20" stroke-miterlimit="10" stroke-width="10"></circle>
</svg>
</div>-->
<!-- ============================================================== -->
<!-- Wrapper -->
<!-- ============================================================== -->
<section id="wrapper">
<div class="error-box">
<div class="error-body text-center">
<h1 class="text-danger">' . $error . '</h1>
<h2 class="text-uppercase" lang="en">' . $errorDetails['type'] . '</h2>
<h3 class="text-uppercase" lang="en">' . $errorDetails['description'] . '</h3>
<p class="text-muted m-t-30 m-b-30">Hey there, ' . $Organizr->user['username'] . '. Looks like you tried accessing something that just ain\'t right! WTF right?! </p>
<a href="' . $nonRootPath . '" class="btn btn-danger btn-rounded waves-effect waves-light m-b-40">Back Home</a>
</div>
</div>
</section>
<script>
languageList = ' . $Organizr->languagePacks(true) . '
var langStrings = { "token": {} };
var lang = new Lang();
loadLanguageList();
lang.init({
currentLang: (getCookie("organizrLanguage")) ? getCookie("organizrLanguage") : "en",
cookie: {
name: "organizrLanguage",
expiry: 365,
path: "/"
},
allowCookieOverride: true
});
$.urlParam = function(name){
let results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(window.location.href);
if (results == null) {
return null;
} else {
return decodeURI(results[1]) || 0;
}
};
if ($.urlParam("return") !== null && "' . $Organizr->user['groupID'] . '" === "999") {
local("set", "uri", $.urlParam("return"));
}
function localStorageSupport() {
return (("localStorage" in window) && window["localStorage"] !== null)
}
function local(type,key,value=null){
if (localStorageSupport) {
switch (type) {
case "set":
case "s":
localStorage.setItem(key,value);
break;
case "get":
case "g":
return localStorage.getItem(key);
break;
case "remove":
case "r":
localStorage.removeItem(key);
break;
}
}
}
function loadLanguageList(){
$.each(languageList, function(i,v) {
lang.dynamic(v.code, "' . $nonRootPath . 'js/langpack/"+v.filename);
});
}
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(";");
for(var i = 0; i <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == " ") {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
</script>
</body>
</html>
';
}

Some files were not shown because too many files have changed in this diff Show More