diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81f8b2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/.vscode/ +/vendor/ + +/database/yggtracker.mwb.bak +/src/config/app.php + +/composer.lock \ No newline at end of file diff --git a/README.md b/README.md index 2e96018..6411e6a 100644 --- a/README.md +++ b/README.md @@ -1 +1,103 @@ -# YGGtracker \ No newline at end of file +# YGGtracker + +BitTorrent Catalog for Yggdrasil ecosystem + +YGGtracker uses [Yggdrasil](https://github.com/yggdrasil-network/yggdrasil-go) IPv6 addresses to identify users without registration. + +#### Online instances + + * [http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker](http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker/) + * [http://94.140.114.241/yggtracker](http://94.140.114.241/yggtracker/) + +#### Trackers + + * `http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker/announce` [stats](http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker/stats) + * `http://[200:1e2f:e608:eb3a:2bf:1e62:87ba:e2f7]/announce` [stats](http://[200:1e2f:e608:eb3a:2bf:1e62:87ba:e2f7]/stats) + +#### Requirements + +``` +php8^ +php-pdo +php-mysql +sphinxsearch +``` +#### Installation + +* `git clone https://github.com/YGGverse/YGGtracker.git` +* `cd YGGtracker` +* `composer update` + +#### Setup +* Server configuration `/example/environment` +* The web root dir is `/src/public` +* Deploy the database using [MySQL Workbench](https://www.mysql.com/products/workbench) project presented in the `/database` folder +* Install [Sphinx Search Server](https://sphinxsearch.com) +* Configuration examples presented at `/config` folder +* Set up the `/src/crontab` by following [example](https://github.com/YGGverse/YGGtracker/blob/main/%20example/environment%20/crontab) + +#### Contribute + +Please make new branch for each PR + +``` +git checkout main +git checkout -b my-pr-branch-name +``` + +#### Roadmap + +* [ ] Magnet + + [x] Options + + [x] Public + + [x] Sensitive + + [x] Comments + + [ ] Features + + [x] Stars + + [x] Downloads + + [ ] Comments + + [ ] Views + + [ ] Info page + +* [ ] User + + [ ] Magnets + + [ ] Downloads + + [ ] Stars + + [ ] Following + + [ ] Followers + + [ ] Profile menu + + [ ] Magnets + + [ ] Downloads + + [ ] Stars + + [ ] Comments + +* [x] Other + + [x] RSS + + [x] Moderation + +#### Donate to contributors + +* @d47081: [BTC](https://www.blockchain.com/explorer/addresses/btc/bc1qngdf2kwty6djjqpk0ynkpq9wmlrmtm7e0c534y) | [LTC](https://live.blockcypher.com/ltc/address/LUSiqzKsfB1vBLvpu515DZktG9ioKqLyj7) | [XMR](835gSR1Uvka19gnWPkU2pyRozZugRZSPHDuFL6YajaAqjEtMwSPr4jafM8idRuBWo7AWD3pwFQSYRMRW9XezqrK4BEXBgXE) | [ZEPH](ZEPHsADHXqnhfWhXrRcXnyBQMucE3NM7Ng5ZVB99XwA38PTnbjLKpCwcQVgoie8EJuWozKgBiTmDFW4iY7fNEgSEWyAy4dotqtX) | [DOGE](https://dogechain.info/address/D5Sez493ibLqTpyB3xwQUspZvJ1cxEdRNQ) | Support our server by order [Linux VPS](https://www.yourserver.se/portal/aff.php?aff=610) + +#### License + +* Engine sources [MIT License](https://github.com/YGGverse/YGGtracker/blob/main/LICENSE) + +#### Components + +[Icons](https://icons.getbootstrap.com) + +#### Feedback + +Feel free to [share](https://github.com/YGGverse/YGGtracker/issues) your ideas and bug reports! + +#### Community + +* [Mastodon](https://mastodon.social/@YGGverse) +* [[matrix]](https://matrix.to/#/#YGGtracker:matrix.org) + +#### See also + +* [YGGo - YGGo! Distributed Web Search Engine ](https://github.com/YGGverse/YGGo) +* [YGGwave ~ The Radio Catalog](https://github.com/YGGverse/YGGwave) +* [YGGstate - Yggdrasil Network Explorer](https://github.com/YGGverse/YGGstate) \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..1deb105 --- /dev/null +++ b/composer.json @@ -0,0 +1,21 @@ +{ + "name": "yggverse/yggtracker", + "description": "Public BitTorrent tracker for Yggdrasil network", + "type": "library", + "require": { + "php": "^8.1", + "yggverse/parser": ">=0.2.0" + }, + "license": "MIT", + "autoload": { + "psr-4": { + "Yggverse\\Yggtracker\\": "src/" + } + }, + "authors": [ + { + "name": "YGGverse" + } + ], + "minimum-stability": "alpha" +} diff --git a/database/yggtracker.mwb b/database/yggtracker.mwb new file mode 100644 index 0000000..f069686 Binary files /dev/null and b/database/yggtracker.mwb differ diff --git a/example/environment /crontab b/example/environment /crontab new file mode 100644 index 0000000..0ec8c1d --- /dev/null +++ b/example/environment /crontab @@ -0,0 +1,4 @@ +@reboot searchd +@reboot indexer --all --rotate + +* * * * * indexer magnet --rotate \ No newline at end of file diff --git a/example/environment /sphinx.conf b/example/environment /sphinx.conf new file mode 100644 index 0000000..c8e5089 --- /dev/null +++ b/example/environment /sphinx.conf @@ -0,0 +1,52 @@ +source yggtracker +{ + type = mysql + + sql_port = 3306 + sql_host = localhost + sql_user = + sql_pass = + sql_db = +} + +source magnet : yggtracker +{ + sql_query = \ + SELECT `magnet`.`timeAdded`, \ + `magnet`.`timeUpdated`, \ + `magnet`.`magnetId`, \ + `magnet`.`metaTitle`, \ + `magnet`.`metaDescription`, \ + `magnet`.`dn`, \ + (SELECT GROUP_CONCAT(DISTINCT `keywordTopic`.`value`) \ + FROM `keywordTopic` \ + JOIN `magnetToKeywordTopic` ON (`magnetToKeywordTopic`.`magnetId` = `magnet`.`magnetId`) \ + WHERE `keywordTopic`.`keywordTopicId` = `magnetToKeywordTopic`.`keywordTopicId`) AS `keywords`, \ + (SELECT GROUP_CONCAT(DISTINCT `magnetComment`.`value`) \ + FROM `magnetComment` \ + WHERE `magnetComment`.`magnetId` = `magnet`.`magnetId`) AS `comments` \ + FROM `magnet`\ + + sql_attr_uint = magnetId +} + +index magnet +{ + source = magnet + path = /var/lib/sphinxsearch/data/magnet + + morphology = stem_cz, stem_ar, lemmatize_de_all, lemmatize_ru_all, lemmatize_en_all # stem_enru + + min_word_len = 2 + min_prefix_len = 2 + + html_strip = 1 + + index_exact_words = 1 +} + +indexer +{ + mem_limit = 256M + lemmatizer_cache = 256M +} \ No newline at end of file diff --git a/src/config/app.php.example b/src/config/app.php.example new file mode 100644 index 0000000..7761939 --- /dev/null +++ b/src/config/app.php.example @@ -0,0 +1,102 @@ + (object) + [ + 'announce' => 'http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker/announce', + 'stats' => 'http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker/stats', + ], + 'Tracker 2' => (object) + [ + 'announce' => 'http://[200:1e2f:e608:eb3a:2bf:1e62:87ba:e2f7]/announce', + 'stats' => 'http://[200:1e2f:e608:eb3a:2bf:1e62:87ba:e2f7]/stats', + ], + // ... + ] +); + +// Yggdrasil +define('YGGDRASIL_URL_REGEX', '/^0{0,1}[2-3][a-f0-9]{0,2}:/'); // thanks to @ygguser (https://github.com/YGGverse/YGGo/issues/1#issuecomment-1498182228 ) diff --git a/src/library/database.php b/src/library/database.php new file mode 100644 index 0000000..4c8dccc --- /dev/null +++ b/src/library/database.php @@ -0,0 +1,1032 @@ +_db = new PDO('mysql:dbname=' . $database . ';host=' . $host . ';port=' . $port . ';charset=utf8', $username, $password, [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8']); + $this->_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->_db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + $this->_db->setAttribute(PDO::ATTR_TIMEOUT, 600); + + $this->_debug = (object) + [ + 'query' => (object) + [ + 'select' => (object) + [ + 'total' => 0 + ], + 'insert' => (object) + [ + 'total' => 0 + ], + 'update' => (object) + [ + 'total' => 0 + ], + 'delete' => (object) + [ + 'total' => 0 + ], + ] + ]; + } + + // Tools + public function beginTransaction() { + + $this->_db->beginTransaction(); + } + + public function commit() { + + $this->_db->commit(); + } + + public function rollBack() { + + $this->_db->rollBack(); + } + + public function getDebug() { + + return $this->_debug; + } + + // Scheme + public function addScheme(string $value) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `scheme` SET `value` = ?'); + + $query->execute([$value]); + + return $this->_db->lastInsertId(); + } + + public function getScheme(int $schemeId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `scheme` WHERE `schemeId` = ?'); + + $query->execute([$schemeId]); + + return $query->fetch(); + } + + public function findScheme(string $value) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `scheme` WHERE `value` = ?'); + + $query->execute([$value]); + + return $query->fetch(); + } + + public function initSchemeId(string $value) : int { + + if ($result = $this->findScheme($value)) { + + return $result->schemeId; + } + + return $this->addScheme($value); + } + + // Host + public function addHost(string $value) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `host` SET `value` = ?'); + + $query->execute([$value]); + + return $this->_db->lastInsertId(); + } + + public function getHost(int $hostId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `host` WHERE `hostId` = ?'); + + $query->execute([$hostId]); + + return $query->fetch(); + } + + public function findHost(string $value) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `host` WHERE `value` = ?'); + + $query->execute([$value]); + + return $query->fetch(); + } + + public function initHostId(string $value) : int { + + if ($result = $this->findHost($value)) { + + return $result->hostId; + } + + return $this->addHost($value); + } + + // Port + public function addPort(mixed $value) : int { + + $this->_debug->query->insert->total++; + + if ($value) { + + $query = $this->_db->prepare('INSERT INTO `port` SET `value` = ?'); + + $query->execute([$value]); + + } else { + + $query = $this->_db->prepare('INSERT INTO `port` SET `value` = NULL'); + + $query->execute(); + } + + return $this->_db->lastInsertId(); + } + + public function getPort(int $portId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `port` WHERE `portId` = ?'); + + $query->execute([$portId]); + + return $query->fetch(); + } + + public function findPort(mixed $value) { + + $this->_debug->query->select->total++; + + if ($value) { + + $query = $this->_db->prepare('SELECT * FROM `port` WHERE `value` = ?'); + + $query->execute([$value]); + + } else { + + $query = $this->_db->prepare('SELECT * FROM `port` WHERE `value` IS NULL'); + + $query->execute(); + } + + return $query->fetch(); + } + + public function initPortId(mixed $value) : int { + + if ($result = $this->findPort($value)) { + + return $result->portId; + } + + return $this->addPort($value); + } + + // URI + public function addUri(string $value) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `uri` SET `value` = ?'); + + $query->execute([$value]); + + return $this->_db->lastInsertId(); + } + + public function getUri(int $uriId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `uri` WHERE `uriId` = ?'); + + $query->execute([$uriId]); + + return $query->fetch(); + } + + public function findUri(string $value) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `uri` WHERE `value` = ?'); + + $query->execute([$value]); + + return $query->fetch(); + } + + public function initUriId(string $value) : int { + + if ($result = $this->findUri($value)) { + + return $result->uriId; + } + + return $this->addUri($value); + } + + // Address Tracker + public function addAddressTracker(int $schemeId, int $hostId, int $portId, int $uriId) : int { + + $this->_debug->query->insert->total++; + + if ($portId) { + + $query = $this->_db->prepare('INSERT INTO `addressTracker` SET `schemeId` = ?, `hostId` = ?, `portId` = ?, `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $portId, $uriId]); + + } else { + + $query = $this->_db->prepare('INSERT INTO `addressTracker` SET `schemeId` = ?, `hostId` = ?, `portId` = NULL, `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $uriId]); + } + + return $this->_db->lastInsertId(); + } + + public function getAddressTracker(int $addressTrackerId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `addressTracker` WHERE `addressTrackerId` = ?'); + + $query->execute([$addressTrackerId]); + + return $query->fetch(); + } + + public function findAddressTracker(int $schemeId, int $hostId, mixed $portId, int $uriId) { + + $this->_debug->query->select->total++; + + if ($portId) { + + $query = $this->_db->prepare('SELECT * FROM `addressTracker` WHERE `schemeId` = ? AND `hostId` = ? AND `portId` = ? AND `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $portId, $uriId]); + + } else { + + $query = $this->_db->prepare('SELECT * FROM `addressTracker` WHERE `schemeId` = ? AND `hostId` = ? AND `portId` IS NULL AND `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $uriId]); + } + + return $query->fetch(); + } + + public function initAddressTrackerId(int $schemeId, int $hostId, mixed $portId, int $uriId) : int { + + if ($result = $this->findAddressTracker($schemeId, $hostId, $portId, $uriId)) { + + return $result->addressTrackerId; + } + + return $this->addAddressTracker($schemeId, $hostId, $portId, $uriId); + } + + // Acceptable Source + public function addAcceptableSource(int $schemeId, int $hostId, int $portId, int $uriId) : int { + + $this->_debug->query->insert->total++; + + if ($portId) { + + $query = $this->_db->prepare('INSERT INTO `acceptableSource` SET `schemeId` = ?, `hostId` = ?, `portId` = ?, `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $portId, $uriId]); + + } else { + + $query = $this->_db->prepare('INSERT INTO `acceptableSource` SET `schemeId` = ?, `hostId` = ?, `portId` = NULL, `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $uriId]); + } + + return $this->_db->lastInsertId(); + } + + public function getAcceptableSource(int $acceptableSourceId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `acceptableSource` WHERE `acceptableSourceId` = ?'); + + $query->execute([$acceptableSourceId]); + + return $query->fetch(); + } + + public function findAcceptableSource(int $schemeId, int $hostId, mixed $portId, int $uriId) { + + $this->_debug->query->select->total++; + + if ($portId) { + + $query = $this->_db->prepare('SELECT * FROM `acceptableSource` WHERE `schemeId` = ? AND `hostId` = ? AND `portId` = ? AND `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $portId, $uriId]); + + } else { + + $query = $this->_db->prepare('SELECT * FROM `acceptableSource` WHERE `schemeId` = ? AND `hostId` = ? AND `portId` IS NULL AND `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $uriId]); + } + + return $query->fetch(); + } + + public function initAcceptableSourceId(int $schemeId, int $hostId, mixed $portId, int $uriId) : int { + + if ($result = $this->findAcceptableSource($schemeId, $hostId, $portId, $uriId)) { + + return $result->acceptableSourceId; + } + + return $this->addAcceptableSource($schemeId, $hostId, $portId, $uriId); + } + + // eXact Source + public function addExactSource(int $schemeId, int $hostId, int $portId, int $uriId) : int { + + $this->_debug->query->insert->total++; + + if ($portId) { + + $query = $this->_db->prepare('INSERT INTO `eXactSource` SET `schemeId` = ?, `hostId` = ?, `portId` = ?, `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $portId, $uriId]); + + } else { + + $query = $this->_db->prepare('INSERT INTO `eXactSource` SET `schemeId` = ?, `hostId` = ?, `portId` = NULL, `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $uriId]); + } + + return $this->_db->lastInsertId(); + } + + public function getExactSource(int $eXactSourceId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `eXactSource` WHERE `eXactSourceId` = ?'); + + $query->execute([$eXactSourceId]); + + return $query->fetch(); + } + + public function findExactSource(int $schemeId, int $hostId, mixed $portId, int $uriId) { + + $this->_debug->query->select->total++; + + if ($portId) { + + $query = $this->_db->prepare('SELECT * FROM `eXactSource` WHERE `schemeId` = ? AND `hostId` = ? AND `portId` = ? AND `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $portId, $uriId]); + + } else { + + $query = $this->_db->prepare('SELECT * FROM `eXactSource` WHERE `schemeId` = ? AND `hostId` = ? AND `portId` IS NULL AND `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $uriId]); + } + + return $query->fetch(); + } + + public function initExactSourceId(int $schemeId, int $hostId, mixed $portId, int $uriId) : int { + + if ($result = $this->findExactSource($schemeId, $hostId, $portId, $uriId)) { + + return $result->eXactSourceId; + } + + return $this->addExactSource($schemeId, $hostId, $portId, $uriId); + } + + // Keyword Topic + public function addKeywordTopic(string $value) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `keywordTopic` SET `value` = ?'); + + $query->execute([$value]); + + return $this->_db->lastInsertId(); + } + + public function findKeywordTopic(string $value) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `keywordTopic` WHERE `value` = ?'); + + $query->execute([$value]); + + return $query->fetch(); + } + + public function getKeywordTopic(int $keywordTopicId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `keywordTopic` WHERE `keywordTopicId` = ?'); + + $query->execute([$keywordTopicId]); + + return $query->fetch(); + } + + public function initKeywordTopicId(string $value) : int { + + if ($result = $this->findKeywordTopic($value)) { + + return $result->keywordTopicId; + } + + return $this->addKeywordTopic($value); + } + + // User + public function addUser(string $address, bool $approved, $timeAdded) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `user` SET `address` = ?, `approved` = ?, `timeAdded` = ?'); + + $query->execute([$address, (int) $approved, $timeAdded]); + + return $this->_db->lastInsertId(); + } + + public function getUser(int $userId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `user` WHERE `userId` = ?'); + + $query->execute([$userId]); + + return $query->fetch(); + } + + public function findUserByAddress(string $address) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `user` WHERE `address` = ?'); + + $query->execute([$address]); + + return $query->fetch(); + } + + public function initUserId(string $address, bool $approved, int $timeAdded) : int { + + if ($result = $this->findUserByAddress($address)) { + + return $result->userId; + } + + return $this->addUser($address, $approved, $timeAdded); + } + + // Magnet + public function addMagnet(int $userId, + string $xt, + int $xl, + string $dn, + string $linkSource, + bool $public, + bool $comments, + bool $sensitive, + bool $approved, + int $timeAdded) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnet` SET `userId` = ?, + `xt` = ?, + `xl` = ?, + `dn` = ?, + `linkSource` = ?, + `public` = ?, + `comments` = ?, + `sensitive` = ?, + `approved` = ?, + `timeAdded` = ?'); + + $query->execute( + [ + $userId, + $xt, + $xl, + $dn, + $linkSource, + $public ? 1 : 0, + $comments ? 1 : 0, + $sensitive ? 1 : 0, + $approved ? 1 : 0, + $timeAdded + ] + ); + + return $this->_db->lastInsertId(); + } + + public function getMagnet(int $magnetId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnet` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetch(); + } + + public function findMagnet(int $userId, string $xt) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnet` WHERE `userId` = ? AND `xt` = ?'); + + $query->execute([$userId, $xt]); + + return $query->fetch(); + } + + public function initMagnetId( int $userId, + string $xt, + int $xl, + string $dn, + string $linkSource, + bool $public, + bool $comments, + bool $sensitive, + bool $approved, + int $timeAdded) : int { + + if ($result = $this->findMagnet($userId, $xt)) { + + return $result->magnetId; + } + + return $this->addMagnet($userId, + $xt, + $xl, + $dn, + $linkSource, + $public, + $comments, + $sensitive, + $approved, + $timeAdded); + } + + public function updateMagnetDn(int $magnetId, string $dn, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `dn` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([$dn, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + public function updateMagnetMetaTitle(int $magnetId, string $metaTitle, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `metaTitle` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([$metaTitle, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + public function updateMagnetMetaDescription(int $magnetId, string $metaDescription, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `metaDescription` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([$metaDescription, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + public function updateMagnetPublic(int $magnetId, bool $public, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `public` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([(int) $public, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + public function updateMagnetComments(int $magnetId, bool $comments, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `comments` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([(int) $comments, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + public function updateMagnetSensitive(int $magnetId, bool $sensitive, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `sensitive` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([(int) $sensitive, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + public function updateMagnetApproved(int $magnetId, bool $approved, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `approved` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([(int) $approved, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + // Magnet to AddressTracker + public function addMagnetToAddressTracker(int $magnetId, int $addressTrackerId) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnetToAddressTracker` SET `magnetId` = ?, `addressTrackerId` = ?'); + + $query->execute([$magnetId, $addressTrackerId]); + + return $this->_db->lastInsertId(); + } + + public function deleteMagnetToAddressTrackerByMagnetId(int $magnetId) : int { + + $this->_debug->query->delete->total++; + + $query = $this->_db->prepare('DELETE FROM `magnetToAddressTracker` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->rowCount(); + } + + public function findMagnetToAddressTracker(int $magnetId, int $addressTrackerId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToAddressTracker` WHERE `magnetId` = ? AND `addressTrackerId` = ?'); + + $query->execute([$magnetId, $addressTrackerId]); + + return $query->fetch(); + } + + public function findAddressTrackerByMagnetId(int $magnetId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToAddressTracker` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetchAll(); + } + + public function initMagnetToAddressTrackerId(int $magnetId, int $addressTrackerId) : int { + + if ($result = $this->findMagnetToAddressTracker($magnetId, $addressTrackerId)) { + + return $result->magnetToAddressTrackerId; + } + + return $this->addMagnetToAddressTracker($magnetId, $addressTrackerId); + } + + // Magnet to AcceptableSource + public function addMagnetToAcceptableSource(int $magnetId, int $acceptableSourceId) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnetToAcceptableSource` SET `magnetId` = ?, `acceptableSourceId` = ?'); + + $query->execute([$magnetId, $acceptableSourceId]); + + return $this->_db->lastInsertId(); + } + + public function deleteMagnetToAcceptableSourceByMagnetId(int $magnetId) : int { + + $this->_debug->query->delete->total++; + + $query = $this->_db->prepare('DELETE FROM `magnetToAcceptableSource` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->rowCount(); + } + + public function findMagnetToAcceptableSource(int $magnetId, int $acceptableSourceId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToAcceptableSource` WHERE `magnetId` = ? AND `acceptableSourceId` = ?'); + + $query->execute([$magnetId, $acceptableSourceId]); + + return $query->fetch(); + } + + public function findAcceptableSourceByMagnetId(int $magnetId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToAcceptableSource` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetchAll(); + } + + public function initMagnetToAcceptableSourceId(int $magnetId, int $acceptableSourceId) : int { + + if ($result = $this->findMagnetToAcceptableSource($magnetId, $acceptableSourceId)) { + + return $result->magnetToAcceptableSourceId; + } + + return $this->addMagnetToAcceptableSource($magnetId, $acceptableSourceId); + } + + // Magnet to eXactSource + public function addMagnetToExactSource(int $magnetId, int $eXactSourceId) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnetToExactSource` SET `magnetId` = ?, `eXactSourceId` = ?'); + + $query->execute([$magnetId, $eXactSourceId]); + + return $this->_db->lastInsertId(); + } + + + public function deleteMagnetToExactSourceByMagnetId(int $magnetId) : int { + + $this->_debug->query->delete->total++; + + $query = $this->_db->prepare('DELETE FROM `magnetToExactSource` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $this->_db->lastInsertId(); + } + + public function findMagnetToExactSource(int $magnetId, int $eXactSourceId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToExactSource` WHERE `magnetId` = ? AND `eXactSourceId` = ?'); + + $query->execute([$magnetId, $eXactSourceId]); + + return $query->fetch(); + } + + public function findExactSourceByMagnetId(int $magnetId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToExactSource` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetchAll(); + } + + public function initMagnetToExactSourceId(int $magnetId, int $eXactSourceId) : int { + + if ($result = $this->findMagnetToEXactSource($magnetId, $eXactSourceId)) { + + return $result->magnetToExactSourceId; + } + + return $this->addMagnetToEXactSource($magnetId, $eXactSourceId); + } + + // Magnet to KeywordTopic + public function addMagnetToKeywordTopic(int $magnetId, int $keywordTopicId) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnetToKeywordTopic` SET `magnetId` = ?, `keywordTopicId` = ?'); + + $query->execute([$magnetId, $keywordTopicId]); + + return $this->_db->lastInsertId(); + } + + public function deleteMagnetToKeywordTopicByMagnetId(int $magnetId) : int { + + $this->_debug->query->delete->total++; + + $query = $this->_db->prepare('DELETE FROM `magnetToKeywordTopic` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->rowCount(); + } + + public function findMagnetToKeywordTopic(int $magnetId, int $keywordTopicId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToKeywordTopic` WHERE `magnetId` = ? AND `keywordTopicId` = ?'); + + $query->execute([$magnetId, $keywordTopicId]); + + return $query->fetch(); + } + + public function findKeywordTopicByMagnetId(int $magnetId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToKeywordTopic` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetchAll(); + } + + public function initMagnetToKeywordTopicId(int $magnetId, int $keywordTopicId) : int { + + if ($result = $this->findMagnetToKeywordTopic($magnetId, $keywordTopicId)) { + + return $result->magnetToKeywordTopicId; + } + + return $this->addMagnetToKeywordTopic($magnetId, $keywordTopicId); + } + + // Magnet comment + public function getMagnetCommentsTotal(int $magnetId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetComment` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetch()->result; + } + + public function findMagnetCommentsTotalByUserId(int $magnetId, int $userId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetComment` WHERE `magnetId` = ? AND `userId` = ?'); + + $query->execute([$magnetId, $userId]); + + return $query->fetch()->result; + } + + // Magnet star + public function addMagnetStar(int $magnetId, int $userId, int $timeAdded) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnetStar` SET `magnetId` = ?, `userId` = ?, `timeAdded` = ?'); + + $query->execute([$magnetId, $userId, $timeAdded]); + + return $this->_db->lastInsertId(); + } + + public function deleteMagnetStarByUserId(int $magnetId, int $userId) : int { + + $this->_debug->query->delete->total++; + + $query = $this->_db->prepare('DELETE FROM `magnetStar` WHERE `magnetId` = ? AND `userId` = ?'); + + $query->execute([$magnetId, $userId]); + + return $query->rowCount(); + } + + public function getMagnetStarsTotal(int $magnetId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetStar` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetch()->result; + } + + public function findMagnetStarsTotalByUserId(int $magnetId, int $userId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetStar` WHERE `magnetId` = ? AND `userId` = ?'); + + $query->execute([$magnetId, $userId]); + + return $query->fetch()->result; + } + + // Magnet download + public function addMagnetDownload(int $magnetId, int $userId, int $timeAdded) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnetDownload` SET `magnetId` = ?, `userId` = ?, `timeAdded` = ?'); + + $query->execute([$magnetId, $userId, $timeAdded]); + + return $this->_db->lastInsertId(); + } + + public function getMagnetDownloadsTotal(int $magnetId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetDownload` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetch()->result; + } + + public function deleteMagnetDownloadByUserId(int $magnetId, int $userId) : int { + + $this->_debug->query->delete->total++; + + $query = $this->_db->prepare('DELETE FROM `magnetDownload` WHERE `magnetId` = ? AND `userId` = ?'); + + $query->execute([$magnetId, $userId]); + + return $query->rowCount(); + } + + public function findMagnetDownloadsTotalByUserId(int $magnetId, int $userId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetDownload` WHERE `magnetId` = ? AND `userId` = ?'); + + $query->execute([$magnetId, $userId]); + + return $query->fetch()->result; + } +} \ No newline at end of file diff --git a/src/library/sphinx.php b/src/library/sphinx.php new file mode 100644 index 0000000..2eff9aa --- /dev/null +++ b/src/library/sphinx.php @@ -0,0 +1,49 @@ +_sphinx = new PDO('mysql:host=' . $host . ';port=' . $port . ';charset=utf8', false, false, [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8']); + $this->_sphinx->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->_sphinx->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + } + + public function searchMagnetsTotal(string $keyword) { + + $query = $this->_sphinx->prepare('SELECT COUNT(*) AS `total` FROM `magnet` WHERE MATCH(?)'); + + $query->execute( + [ + $keyword + ] + ); + + return $query->fetch()->total; + } + + public function searchMagnets(string $keyword, int $start, int $limit, int $maxMatches) { + + $query = $this->_sphinx->prepare("SELECT * + + FROM `magnet` + + WHERE MATCH(?) + + ORDER BY `magnetId` DESC, WEIGHT() DESC + + LIMIT " . (int) ($start >= $maxMatches ? ($maxMatches > 0 ? $maxMatches - 1 : 0) : $start) . "," . (int) $limit . " + + OPTION `max_matches`=" . (int) ($maxMatches >= 1 ? $maxMatches : 1)); + + $query->execute( + [ + $keyword + ] + ); + + return $query->fetchAll(); + } +} diff --git a/src/library/time.php b/src/library/time.php new file mode 100644 index 0000000..9199d3c --- /dev/null +++ b/src/library/time.php @@ -0,0 +1,45 @@ + _('year'), + 30 * 24 * 60 * 60 => _('month'), + 24 * 60 * 60 => _('day'), + 60 * 60 => _('hour'), + 60 => _('minute'), + 1 => _('second') + ]; + + $plural = [ + _('year') => _('years'), + _('month') => _('months'), + _('day') => _('days'), + _('hour') => _('hours'), + _('minute') => _('minutes'), + _('second') => _('seconds') + ]; + + foreach ($values as $key => $value) + { + $result = $diff / $key; + + if ($result >= 1) + { + $round = round($result); + + return sprintf('%s %s ago', $round, $round > 1 ? $plural[$value] : $value); + } + } + } +} diff --git a/src/public/action.php b/src/public/action.php new file mode 100644 index 0000000..58a9763 --- /dev/null +++ b/src/public/action.php @@ -0,0 +1,442 @@ + true, + 'message' => _('Internal server error') +]; + +// Begin action request +switch (isset($_GET['target']) ? urldecode($_GET['target']) : false) +{ + case 'comment': + + break; + + case 'star': + + // Yggdrasil connections only + if (!preg_match(YGGDRASIL_URL_REGEX, $_SERVER['REMOTE_ADDR'])) + { + $response->success = false; + $response->message = _('Yggdrasil connection required for this action'); + } + + // Init session + else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) + { + $response->success = false; + $response->message = _('Could not init user session'); + } + + // Magnet exists + else if (!$magnet = $db->getMagnet(isset($_GET['magnetId']) && $_GET['magnetId'] > 0 ? (int) $_GET['magnetId'] : 0)) + { + $response->success = false; + $response->message = _('Requested magnet not found'); + } + + // Access allowed + else if (!($_SERVER['REMOTE_ADDR'] == $db->getUser($magnet->userId)->address || in_array($_SERVER['REMOTE_ADDR'], MODERATOR_IP_LIST) || ($magnet->public && $magnet->approved))) { + + $response->success = false; + $response->message = _('Magnet not available for this action'); + } + + // Validate callback + else if (empty($_GET['callback'])) + { + $response->success = false; + $response->message = _('Callback required'); + } + + // Validate base64 + else if (!$callback = (string) @base64_decode($_GET['callback'])) + { + $response->success = false; + $response->message = _('Invalid callback encoding'); + } + + // Request valid + else + { + // Star exists, trigger delete + if ($db->findMagnetStarsTotalByUserId($magnet->magnetId, $userId)) + { + $db->deleteMagnetStarByUserId($magnet->magnetId, $userId); + } + else + { + // Star not exists, trigger add + $db->addMagnetStar($magnet->magnetId, $userId, time()); + } + + // Redirect to edit page + header( + sprintf('Location: %s', $callback) + ); + } + + break; + + case 'download': + + // Yggdrasil connections only + if (!preg_match(YGGDRASIL_URL_REGEX, $_SERVER['REMOTE_ADDR'])) + { + $response->success = false; + $response->message = _('Yggdrasil connection required for this action'); + } + + // Init session + else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) + { + $response->success = false; + $response->message = _('Could not init user session'); + } + + // Magnet exists + else if (!$magnet = $db->getMagnet(isset($_GET['magnetId']) && $_GET['magnetId'] > 0 ? (int) $_GET['magnetId'] : 0)) + { + $response->success = false; + $response->message = _('Requested magnet not found'); + } + + // Access allowed + else if (!($_SERVER['REMOTE_ADDR'] == $db->getUser($magnet->userId)->address || in_array($_SERVER['REMOTE_ADDR'], MODERATOR_IP_LIST) || ($magnet->public && $magnet->approved))) { + + $response->success = false; + $response->message = _('Magnet not available for this action'); + } + + // Request valid + else + { + // Download exists, trigger delete + if (!$db->findMagnetDownloadsTotalByUserId($magnet->magnetId, $userId)) + { + // Download not exists, add new record + $db->addMagnetDownload($magnet->magnetId, $userId, time()); + } + + // Build magnet link + $link = []; + + /// Exact Topic + $link[] = sprintf('magnet:?xt=%s', $magnet->xt); + + /// Display Name + $link[] = sprintf('dn=%s', urlencode($magnet->dn)); + + // Keyword Topic + $kt = []; + + foreach ($db->findKeywordTopicByMagnetId($magnet->magnetId) as $result) + { + $kt[] = urlencode($db->getKeywordTopic($result->keywordTopicId)->value); + } + + $link[] = sprintf('kt=%s', implode('+', $kt)); + + /// Address Tracker + foreach ($db->findAddressTrackerByMagnetId($magnet->magnetId) as $result) + { + $addressTracker = $db->getAddressTracker($result->addressTrackerId); + + $scheme = $db->getScheme($addressTracker->schemeId); + $host = $db->getHost($addressTracker->hostId); + $port = $db->getPort($addressTracker->portId); + $uri = $db->getUri($addressTracker->uriId); + + $link[] = sprintf('tr=%s', urlencode($port->value ? sprintf('%s://%s:%s%s', $scheme->value, + $host->value, + $port->value, + $uri->value) : sprintf('%s://%s%s', $scheme->value, + $host->value, + $uri->value))); + } + + /// Acceptable Source + foreach ($db->findAcceptableSourceByMagnetId($magnet->magnetId) as $result) + { + $acceptableSource = $db->getAcceptableSource($result->acceptableSourceId); + + $scheme = $db->getScheme($acceptableSource->schemeId); + $host = $db->getHost($acceptableSource->hostId); + $port = $db->getPort($acceptableSource->portId); + $uri = $db->getUri($acceptableSource->uriId); + + $link[] = sprintf('as=%s', urlencode($port->value ? sprintf('%s://%s:%s%s', $scheme->value, + $host->value, + $port->value, + $uri->value) : sprintf('%s://%s%s', $scheme->value, + $host->value, + $uri->value))); + } + + /// Exact Source + foreach ($db->findExactSourceByMagnetId($magnet->magnetId) as $result) + { + $eXactSource = $db->getExactSource($result->eXactSourceId); + + $scheme = $db->getScheme($eXactSource->schemeId); + $host = $db->getHost($eXactSource->hostId); + $port = $db->getPort($eXactSource->portId); + $uri = $db->getUri($eXactSource->uriId); + + $link[] = sprintf('xs=%s', urlencode($port->value ? sprintf('%s://%s:%s%s', $scheme->value, + $host->value, + $port->value, + $uri->value) : sprintf('%s://%s%s', $scheme->value, + $host->value, + $uri->value))); + } + + // Redirect to edit page + header( + sprintf('Location: %s', implode('&', $link)) + ); + } + + break; + + case 'new': + + // Yggdrasil connections only + if (!preg_match(YGGDRASIL_URL_REGEX, $_SERVER['REMOTE_ADDR'])) + { + $response->success = false; + $response->message = _('Yggdrasil connection required for this action'); + } + + // Init session + else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) + { + $response->success = false; + $response->message = _('Could not init user session'); + } + + // Validate link + if (empty($_GET['magnet'])) + { + $response->success = false; + $response->message = _('Link required'); + } + + // Validate base64 + else if (!$link = (string) @base64_decode($_GET['magnet'])) + { + $response->success = false; + $response->message = _('Invalid link encoding'); + } + + // Validate magnet + else if (!$magnet = Yggverse\Parser\Magnet::parse($link)) + { + $response->success = false; + $response->message = _('Invalid magnet link'); + } + + // Request valid + else + { + // Begin magnet registration + try + { + $db->beginTransaction(); + + // Init magnet + if (Yggverse\Parser\Urn::parse($magnet->xt)) + { + if ($magnetId = $db->initMagnetId($userId, + strip_tags($magnet->xt), + strip_tags($magnet->xl), + strip_tags($magnet->dn), + $link, + MAGNET_DEFAULT_PUBLIC, + MAGNET_DEFAULT_COMMENTS, + MAGNET_DEFAULT_SENSITIVE, + MAGNET_DEFAULT_APPROVED, + time())) + { + foreach ($magnet as $key => $value) + { + switch ($key) + { + case 'tr': + foreach ($value as $tr) + { + if ($url = Yggverse\Parser\Url::parse($tr)) + { + $db->initMagnetToAddressTrackerId( + $magnetId, + $db->initAddressTrackerId( + $db->initSchemeId($url->host->scheme), + $db->initHostId($url->host->name), + $db->initPortId($url->host->port), + $db->initUriId($url->page->uri) + ) + ); + } + } + break; + case 'ws': + foreach ($value as $ws) + { + // @TODO + } + break; + case 'as': + foreach ($value as $as) + { + if ($url = Yggverse\Parser\Url::parse($as)) + { + $db->initMagnetToAcceptableSourceId( + $magnetId, + $db->initAcceptableSourceId( + $db->initSchemeId($url->host->scheme), + $db->initHostId($url->host->name), + $db->initPortId($url->host->port), + $db->initUriId($url->page->uri) + ) + ); + } + } + break; + case 'xs': + foreach ($value as $xs) + { + if ($url = Yggverse\Parser\Url::parse($xs)) + { + $db->initMagnetToExactSourceId( + $magnetId, + $db->initExactSourceId( + $db->initSchemeId($url->host->scheme), + $db->initHostId($url->host->name), + $db->initPortId($url->host->port), + $db->initUriId($url->page->uri) + ) + ); + } + } + break; + case 'mt': + foreach ($value as $mt) + { + // @TODO + } + break; + case 'x.pe': + foreach ($value as $xPe) + { + // @TODO + } + break; + case 'kt': + foreach ($value as $kt) + { + $db->initMagnetToKeywordTopicId( + $magnetId, + $db->initKeywordTopicId(trim(mb_strtolower(strip_tags(html_entity_decode($kt))))) + ); + } + break; + } + } + + $db->commit(); + + // Redirect to edit page + header(sprintf('Location: %s/edit.php?magnetId=%s', trim(WEBSITE_URL, '/'), $magnetId)); + } + } + + } catch (Exception $e) { + + var_dump($e); + + $db->rollBack(); + } + } + + break; + default: + header( + sprintf('Location: %s', WEBSITE_URL) + ); +} + +?> + + + + + + + + <?php echo sprintf(_('Oops - %s'), WEBSITE_NAME) ?> + + + + + + +
+
+ +
+
+
+
+
+
+
+
message ?>
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/public/assets/theme/default/css/common.css b/src/public/assets/theme/default/css/common.css new file mode 100644 index 0000000..c0e8cc4 --- /dev/null +++ b/src/public/assets/theme/default/css/common.css @@ -0,0 +1,71 @@ +* { + border: 0; + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + background: #282b3c; + color: #ccc; + font-family: Sans-serif; + font-size: 13px; +} + +h1, h2, h3, h4, h5 { + display: inline-block; + font-weight: normal; +} + +h2 { + font-size: 16px; +} + +a, +a:visited, +a:active { + color: #96d9a1; + text-decoration: none; + opacity: .9; +} + +a:hover { + opacity: 1; + transition: opacity .5s ease-in-out; +} + +textarea:focus, +input:focus { + outline: none; + color: #fff; +} + +textarea::placeholder, +input::placeholder { + color: #9698a5; + opacity: 1; +} + +input, +textarea { + background: #5d627d; + color: #ccc; + border: 0; + border-radius: 3px; + padding: 6px 8px; + font-size: 13px; +} + +input[type="submit"] { + cursor: pointer; +} + + +header a.logo { + color: #ccc; + font-size: 22px; +} + +header a.logo > span { + color: #96d9a1; +} \ No newline at end of file diff --git a/src/public/assets/theme/default/css/framework.css b/src/public/assets/theme/default/css/framework.css new file mode 100644 index 0000000..bd7f949 --- /dev/null +++ b/src/public/assets/theme/default/css/framework.css @@ -0,0 +1,234 @@ +.container { + position: relative; + overflow: hidden; + max-width: 748px; + margin: 0 auto; +} + +.row { + position: relative; + overflow: hidden; + padding: 8px; +} + +.column { + position: relative; + float: left; +} + +.float-right { + float: right; +} + +.text-center { + text-align: center; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.text-color-green { + color: #96d9a1; +} + +.text-color-red { + color: #d77575; +} + +.text-color-pink { + color: #a44399; +} + +.text-color-blue { + color: #5785b7; +} + +.label { + padding: 4px 8px; + border-radius: 3px; +} + +.label-green { + color: #277b1b; + border: 1px #92bc8c solid; +} + +.line-height-26 { + line-height: 26px; +} + +.border-radius-3 { + border-radius: 3px; +} + +.border-pink-light { + border: 1px #9b6895 solid; +} + +.border-pink { + border: 1px #a44399 solid; +} + +.border-bottom-pink { + border-bottom: 1px #a44399 solid; +} + +.border-bottom-default { + border-bottom: 1px #5d627d solid; +} + +.background-color-night { + background-color: #34384f; +} + +.background-color-red { + background-color: #9b4a4a; +} + +.cursor-default { + cursor: default; +} + +.cursor-help { + cursor: help; +} + +.font-width-normal { + font-weight: normal; +} + +.font-size-12 { + font-size: 12px; +} + +.font-size-22 { + font-size: 22px; +} + +.padding-0 { + padding: 0; +} + +.padding-x-0 { + padding-left: 0; + padding-right: 0; +} + +.padding-4 { + padding: 4px; +} + +.padding-x-4 { + padding-left: 4px; + padding-right: 4px; +} + +.padding-8 { + padding: 8px; +} + +.padding-y-8 { + padding-top: 8px; + padding-bottom: 8px; +} + +.padding-x-16 { + padding-left: 16px; + padding-right: 16px; +} + +.padding-16 { + padding: 16px; +} + +.margin-l-8 { + margin-left: 8px; +} + +.margin-r-8 { + margin-right: 8px; +} + +.margin-l-12 { + margin-left: 12px; +} + +.margin-y-8 { + margin-top: 8px; + margin-bottom: 8px; +} + +.margin-t-8 { + margin-top: 8px; +} + +.margin-b-8 { + margin-bottom: 8px; +} + +.margin-b-16 { + margin-bottom: 16px; +} + +.display-block { + display: block; +} + +.opacity-0 { + opacity: 0; +} + +.opacity-06 { + opacity: .6; +} + +.opacity-hover-1:hover { + opacity: 1; + transition: opacity .2s ease-in-out; +} + +*:hover > .parent-hover-opacity-09 { + opacity: .9; + transition: opacity .2s ease-in-out; +} + +.bloor-2 { + filter: blur(2px); +} + +.bloor-hover-0:hover { + filter: blur(0); +} + +/* responsive rules */ + +.width-100 { + width: 100%; +} + +.width-50 { + width: 50%; +} + +.width-13px { + width: 13px; +} + +@media (max-width: 1220px) { + + .width-tablet-100 { + width: 100%; + } +} + +@media (max-width: 512px) { + + .width-mobile-100 { + width: 100%; + } +} diff --git a/src/public/edit.php b/src/public/edit.php new file mode 100644 index 0000000..d25dcf5 --- /dev/null +++ b/src/public/edit.php @@ -0,0 +1,560 @@ + true, + 'message' => false, + 'form' => (object) + [ + 'metaTitle' => (object) + [ + 'value' => false, + 'valid' => (object) + [ + 'success' => true, + 'message' => false, + ] + ], + 'metaDescription' => (object) + [ + 'value' => false, + 'valid' => (object) + [ + 'success' => true, + 'message' => false, + ] + ], + 'dn' => (object) + [ + 'value' => false, + 'valid' => (object) + [ + 'success' => true, + 'message' => false, + ] + ], + 'kt' => (object) + [ + 'value' => false, + 'valid' => (object) + [ + 'success' => true, + 'message' => false, + ] + ], + 'tr' => (object) + [ + 'value' => false, + 'valid' => (object) + [ + 'success' => true, + 'message' => false, + ] + ], + 'as' => (object) + [ + 'value' => false, + 'valid' => (object) + [ + 'success' => true, + 'message' => false, + ] + ], + 'xs' => (object) + [ + 'value' => false, + 'valid' => (object) + [ + 'success' => true, + 'message' => false, + ] + ], + 'public' => (object) + [ + 'value' => false, + 'valid' => (object) + [ + 'success' => true, + 'message' => false, + ] + ], + 'comments' => (object) + [ + 'value' => false, + 'valid' => (object) + [ + 'success' => true, + 'message' => false, + ] + ], + 'sensitive' => (object) + [ + 'value' => false, + 'valid' => (object) + [ + 'success' => true, + 'message' => false, + ] + ], + ] +]; + +// Yggdrasil connections only +if (!preg_match(YGGDRASIL_URL_REGEX, $_SERVER['REMOTE_ADDR'])) +{ + $response->success = false; + $response->message = _('Yggdrasil connection required to enable resource features'); +} + +// Init session +else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) +{ + $response->success = false; + $response->message = _('Could not init user session'); +} + +// Init magnet +else if (!$magnet = $db->getMagnet(isset($_GET['magnetId']) ? (int) $_GET['magnetId'] : 0)) { + + $response->success = false; + $response->message = _('Magnet not found!'); +} + +// Validate access +else if (!($_SERVER['REMOTE_ADDR'] == $db->getUser($magnet->userId)->address || in_array($_SERVER['REMOTE_ADDR'], MODERATOR_IP_LIST))) { + + $response->success = false; + $response->message = _('You have no permissions to edit this magnet!'); +} + +// Process form +else { + + // Update form + if (!empty($_POST)) { + + // Approve by moderation request + if (isset($_POST['approved']) && in_array($_SERVER['REMOTE_ADDR'], MODERATOR_IP_LIST)) + { + $db->updateMagnetApproved($magnet->magnetId, true, time()); + } + + // Set default approve status + else + { + $db->updateMagnetApproved($magnet->magnetId, MAGNET_DEFAULT_APPROVED, time()); + } + + // Meta + if (MAGNET_META_TITLE_MIN_LENGTH <= mb_strlen($_POST['metaTitle'])) + { + $db->updateMagnetMetaTitle($magnet->magnetId, trim(strip_tags(html_entity_decode($_POST['metaTitle']))), time()); + + $response->form->metaTitle->valid->success = true; + $response->form->metaTitle->valid->message = false; + } + else + { + $response->form->metaTitle->valid->success = false; + $response->form->metaTitle->valid->message = sprintf(_('* required, minimum %s chars'), MAGNET_META_TITLE_MIN_LENGTH); + } + + if (MAGNET_META_DESCRIPTION_MIN_LENGTH <= mb_strlen($_POST['metaDescription'])) + { + $db->updateMagnetMetaDescription($magnet->magnetId, trim(strip_tags(html_entity_decode($_POST['metaDescription']))), time()); + } + else + { + $response->form->metaDescription->valid->success = false; + $response->form->metaDescription->valid->message = sprintf(_('* required, minimum %s chars'), MAGNET_META_DESCRIPTION_MIN_LENGTH); + } + + // Social + $db->updateMagnetPublic($magnet->magnetId, isset($_POST['public']) ? true : false, time()); + $db->updateMagnetComments($magnet->magnetId, isset($_POST['comments']) ? true : false, time()); + $db->updateMagnetSensitive($magnet->magnetId, isset($_POST['sensitive']) ? true : false, time()); + + // Display Name + if (isset($_POST['dn'])) + { + $db->updateMagnetDn($magnet->magnetId, trim(strip_tags(html_entity_decode($_POST['dn']))), time()); + } + + // Keyword Topic + $db->deleteMagnetToKeywordTopicByMagnetId($magnet->magnetId); + + if (!empty($_POST['kt'])) + { + foreach (explode(PHP_EOL, str_replace(['#', ',', ' '], PHP_EOL, $_POST['kt'])) as $kt) + { + if (!empty(trim($kt))) + { + $db->initMagnetToKeywordTopicId( + $magnet->magnetId, + $db->initKeywordTopicId(trim(mb_strtolower(strip_tags(html_entity_decode($kt))))) + ); + } + } + } + + // Address Tracker + $db->deleteMagnetToAddressTrackerByMagnetId($magnet->magnetId); + + if (!empty($_POST['tr'])) + { + $response->form->tr->valid->success = false; + $response->form->tr->valid->message = _('* please, provide at least one Yggdrasil address'); + + foreach (explode(PHP_EOL, str_replace(['#', ',', ' '], PHP_EOL, $_POST['tr'])) as $tr) + { + $tr = trim($tr); + + if (!empty($tr)) + { + if ($url = Yggverse\Parser\Url::parse($tr)) + { + $db->initMagnetToAddressTrackerId( + $magnet->magnetId, + $db->initAddressTrackerId( + $db->initSchemeId($url->host->scheme), + $db->initHostId($url->host->name), + $db->initPortId($url->host->port), + $db->initUriId($url->page->uri) + ) + ); + + if (preg_match(YGGDRASIL_URL_REGEX, $url->host->name)) + { + $response->form->tr->valid->success = true; + $response->form->tr->valid->message = false; + } + } + } + } + } + + // Acceptable Source + $db->deleteMagnetToAcceptableSourceByMagnetId($magnet->magnetId); + + if (!empty($_POST['as'])) + { + $response->form->as->valid->success = false; + $response->form->as->valid->message = _('* please, provide at least one Yggdrasil address'); + + foreach (explode(PHP_EOL, str_replace(['#', ',', ' '], PHP_EOL, $_POST['as'])) as $as) + { + $as = trim($as); + + if (!empty($as)) + { + if ($url = Yggverse\Parser\Url::parse($as)) + { + $db->initMagnetToAcceptableSourceId( + $magnet->magnetId, + $db->initAcceptableSourceId( + $db->initSchemeId($url->host->scheme), + $db->initHostId($url->host->name), + $db->initPortId($url->host->port), + $db->initUriId($url->page->uri) + ) + ); + + if (preg_match(YGGDRASIL_URL_REGEX, $url->host->name)) + { + $response->form->as->valid->success = true; + $response->form->as->valid->message = false; + } + } + } + } + } + + // Exact Source + $db->deleteMagnetToExactSourceByMagnetId($magnet->magnetId); + + if (!empty($_POST['xs'])) + { + $response->form->xs->valid->success = false; + $response->form->xs->valid->message = _('* please, provide at least one Yggdrasil address'); + + foreach (explode(PHP_EOL, str_replace(['#', ',', ' '], PHP_EOL, $_POST['xs'])) as $xs) + { + $xs = trim($xs); + + if (!empty($xs)) + { + if ($url = Yggverse\Parser\Url::parse($xs)) + { + $db->initMagnetToExactSourceId( + $magnet->magnetId, + $db->initExactSourceId( + $db->initSchemeId($url->host->scheme), + $db->initHostId($url->host->name), + $db->initPortId($url->host->port), + $db->initUriId($url->page->uri) + ) + ); + + if (preg_match(YGGDRASIL_URL_REGEX, $url->host->name)) + { + $response->form->xs->valid->success = true; + $response->form->xs->valid->message = false; + } + } + } + } + } + + // Refresh magnet data + $magnet = $db->getMagnet($magnet->magnetId); + } + + // Meta Title, auto-replace with Display Name on empty value + $response->form->metaTitle->value = $magnet->metaTitle ? $magnet->metaTitle : $magnet->dn; + + // Meta Description + $response->form->metaDescription->value = $magnet->metaDescription; + + // Magnet settings + $response->form->public->value = (bool) $magnet->public; + $response->form->comments->value = (bool) $magnet->comments; + $response->form->sensitive->value = (bool) $magnet->sensitive; + + // Display Name + $response->form->dn->value = $magnet->dn; + + // Keyword Topic + $kt = []; + foreach ($db->findKeywordTopicByMagnetId($magnet->magnetId) as $result) + { + $kt[] = $db->getKeywordTopic($result->keywordTopicId)->value; + } + + $response->form->kt->value = implode(', ', $kt); + + // Address Tracker + $tr = []; + foreach ($db->findAddressTrackerByMagnetId($magnet->magnetId) as $result) + { + $addressTracker = $db->getAddressTracker($result->addressTrackerId); + + $scheme = $db->getScheme($addressTracker->schemeId); + $host = $db->getHost($addressTracker->hostId); + $port = $db->getPort($addressTracker->portId); + $uri = $db->getUri($addressTracker->uriId); + + $tr[] = $port->value ? sprintf('%s://%s:%s%s', $scheme->value, + $host->value, + $port->value, + $uri->value) : sprintf('%s://%s%s', $scheme->value, + $host->value, + $uri->value); + } + + $response->form->tr->value = implode(PHP_EOL, $tr); + + // Acceptable Source + $as = []; + foreach ($db->findAcceptableSourceByMagnetId($magnet->magnetId) as $result) + { + $acceptableSource = $db->getAcceptableSource($result->acceptableSourceId); + + $scheme = $db->getScheme($acceptableSource->schemeId); + $host = $db->getHost($acceptableSource->hostId); + $port = $db->getPort($acceptableSource->portId); + $uri = $db->getUri($acceptableSource->uriId); + + $as[] = $port->value ? sprintf('%s://%s:%s%s', $scheme->value, + $host->value, + $port->value, + $uri->value) : sprintf('%s://%s%s', $scheme->value, + $host->value, + $uri->value); + } + + $response->form->as->value = implode(PHP_EOL, $as); + + // Exact Source + $xs = []; + foreach ($db->findExactSourceByMagnetId($magnet->magnetId) as $result) + { + $eXactSource = $db->getExactSource($result->eXactSourceId); + + $scheme = $db->getScheme($eXactSource->schemeId); + $host = $db->getHost($eXactSource->hostId); + $port = $db->getPort($eXactSource->portId); + $uri = $db->getUri($eXactSource->uriId); + + $xs[] = $port->value ? sprintf('%s://%s:%s%s', $scheme->value, + $host->value, + $port->value, + $uri->value) : sprintf('%s://%s%s', $scheme->value, + $host->value, + $uri->value); + } + + $response->form->xs->value = implode(PHP_EOL, $xs); +} + +?> + + + + + + + + <?php echo sprintf(_('Edit - %s'), WEBSITE_NAME) ?> + + + + + + + +
+
+ +
+
+
+
+
+
+ success) { ?> +
+ + +
+
+

+
+
+ + + form->metaTitle->valid->message) { ?> +
form->metaTitle->valid->message ?>
+ + + + form->metaDescription->valid->message) { ?> +
form->metaDescription->valid->message ?>
+ + +
+
+ + + + + + + form->tr->valid->message) { ?> +
form->tr->valid->message ?>
+ + + + form->as->valid->message) { ?> +
form->as->valid->message ?>
+ + + + form->xs->valid->message) { ?> +
form->xs->valid->message ?>
+ + +
+
+ +
+ form->public->value) { ?> + + + + + +
+
+ form->comments->value) { ?> + + + + + +
+
+ form->sensitive->value) { ?> + + + + + +
+ +
+ + + + + + +
+ +
+
+ +
+
+
+ +
+
message ?>
+
+ +
+
+
+
+ + + \ No newline at end of file diff --git a/src/public/index.php b/src/public/index.php new file mode 100644 index 0000000..9aca5f1 --- /dev/null +++ b/src/public/index.php @@ -0,0 +1,336 @@ + false, + 'page' => 1, +]; + +// Prepare request +$request->query = isset($_GET['query']) ? urldecode((string) $_GET['query']) : ''; +$request->page = isset($_GET['page']) && $_GET['page'] > 0 ? (int) $_GET['page'] : 1; + +// Define response +$response = (object) +[ + 'success' => true, + 'message' => false, + 'magnets' => [], +]; + +// Yggdrasil connections only +if (!preg_match(YGGDRASIL_URL_REGEX, $_SERVER['REMOTE_ADDR'])) +{ + $response->success = false; + $response->message = _('Yggdrasil connection required to enable resource features'); +} + +// Init session +else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) +{ + $response->success = false; + $response->message = _('Could not init user session'); +} + +// Request valid +else +{ + // Query is magnet link + if ($magnet = Yggverse\Parser\Magnet::is($request->query)) + { + header( + sprintf('Location: %s/action.php?target=new&magnet=%s', WEBSITE_URL, base64_encode($request->query)) + ); + } + + // Get index + $total = $sphinx->searchMagnetsTotal($request->query); + $results = $sphinx->searchMagnets( + $request->query, + $request->page * WEBSITE_PAGINATION_LIMIT - WEBSITE_PAGINATION_LIMIT, + WEBSITE_PAGINATION_LIMIT, + $total + ); + + foreach ($results as $result) + { + if ($magnet = $db->getMagnet($result->magnetid)) + { + $keywords = []; + + foreach ($db->findKeywordTopicByMagnetId($magnet->magnetId) as $keyword) + { + $keywords[] = $db->getKeywordTopic($keyword->keywordTopicId)->value; + } + + $response->magnets[] = (object) + [ + 'magnetId' => $magnet->magnetId, + 'metaTitle' => $magnet->metaTitle ? htmlentities($magnet->metaTitle) : ($magnet->dn ? htmlentities($magnet->dn): false), + 'metaDescription' => $magnet->metaDescription ? nl2br( + htmlentities( + substr($magnet->metaDescription, 0, WEBSITE_MAGNET_SHORT_META_DESCRIPTION_LENGTH) + ) + ) : false, + 'approved' => (bool) $magnet->approved, + 'public' => (bool) $magnet->public, + 'sensitive' => (bool) $magnet->sensitive, + 'comments' => (bool) $magnet->comments, + 'timeAdded' => Time::ago($magnet->timeAdded), + 'timeUpdated' => Time::ago($magnet->timeUpdated), + 'keywords' => $keywords, + 'comment' => (object) + [ + 'total' => $db->getMagnetCommentsTotal($magnet->magnetId), + 'status' => $db->findMagnetCommentsTotalByUserId($magnet->magnetId, $userId), + ], + 'download' => (object) + [ + 'total' => $db->getMagnetDownloadsTotal($magnet->magnetId), + 'status' => $db->findMagnetDownloadsTotalByUserId($magnet->magnetId, $userId), + ], + 'star' => (object) + [ + 'total' => $db->getMagnetStarsTotal($magnet->magnetId), + 'status' => $db->findMagnetStarsTotalByUserId($magnet->magnetId, $userId), + ], + 'access' => (object) + [ + 'read' => ($_SERVER['REMOTE_ADDR'] == $db->getUser($magnet->userId)->address || in_array($_SERVER['REMOTE_ADDR'], MODERATOR_IP_LIST) || ($magnet->public && $magnet->approved)), + 'edit' => ($_SERVER['REMOTE_ADDR'] == $db->getUser($magnet->userId)->address || in_array($_SERVER['REMOTE_ADDR'], MODERATOR_IP_LIST)), + ], + ]; + } + } +} + +if (isset($_GET['rss']) && $response->success) { ?>' . PHP_EOL ?> + + + + <?php echo WEBSITE_NAME ?> + + /index.phpquery ? sprintf('?query=%s', urlencode($request->query)) : false ?> + magnets as $magnet) { ?> + access->read) { ?> + + <?php echo htmlspecialchars($magnet->metaTitle, ENT_QUOTES, 'UTF-8') ?> + #magnet-magnetId ?> + metaDescription), ENT_QUOTES, 'UTF-8') ?> + #magnet-magnetId ?> + + + + + + + + + + + + + <?php echo sprintf(_('%s - BitTorrent Catalog for Yggdrasil'), WEBSITE_NAME) ?> + + + + + + + +
+
+ +
+
+
+
+
+
+ success) { ?> + magnets) { ?> + magnets as $magnet) { ?> + access->read) { ?> +
+
+ +

metaTitle ?>

+
+ access->edit) { ?> + + + + + + + + + +
+ metaDescription) { ?> +
metaDescription ?>
+ + keywords) { ?> +
+ keywords as $keyword) { ?> + + # + + +
+ +
+
+ public) { ?> + + + + + + + + approved) { ?> + + + + + + + timeUpdated ? sprintf('Updated %s', $magnet->timeUpdated) : sprintf('Added %s', $magnet->timeAdded) ?> + + + star->status) { ?> + + + + + + + + + + star->total ?> + + + + + download->status) { ?> + + + + + + + + + + download->total ?> + +
+
+ +
+
+
+ + + +
+

+
+
+ + +
+
message ?>
+
+ +
+
+
+
+ + + + \ No newline at end of file