2016-10-02 21:07:17 +00:00
< ? php
2016-10-06 02:08:43 +00:00
declare ( strict_types = 1 );
2016-10-02 21:07:17 +00:00
namespace JKingWeb\NewsSync ;
class Database {
2017-02-19 22:02:03 +00:00
use PicoFeed\Reader\Reader ;
use PicoFeed\PicoFeedException ;
2017-02-16 20:29:42 +00:00
const SCHEMA_VERSION = 1 ;
const FORMAT_TS = " Y-m-d h:i:s " ;
const FORMAT_DATE = " Y-m-d " ;
const FORMAT_TIME = " h:i:s " ;
2017-02-19 22:02:03 +00:00
2017-02-16 20:29:42 +00:00
protected $data ;
public $db ;
2017-02-19 22:02:03 +00:00
private $driver ;
2016-10-15 13:45:23 +00:00
2017-02-16 20:29:42 +00:00
protected function cleanName ( string $name ) : string {
return ( string ) preg_filter ( " [^0-9a-zA-Z_ \ .] " , " " , $name );
}
2016-10-02 21:07:17 +00:00
2017-02-16 20:29:42 +00:00
public function __construct ( RuntimeData $data ) {
$this -> data = $data ;
2017-02-19 22:02:03 +00:00
$this -> driver = $data -> conf -> dbDriver ;
$this -> db = $this -> driver :: create ( $data , INSTALL );
2017-02-16 20:29:42 +00:00
$ver = $this -> db -> schemaVersion ();
if ( ! INSTALL && $ver < self :: SCHEMA_VERSION ) {
$this -> db -> update ( self :: SCHEMA_VERSION );
}
}
2016-10-02 21:07:17 +00:00
2017-02-16 20:29:42 +00:00
static public function listDrivers () : array {
$sep = \DIRECTORY_SEPARATOR ;
$path = __DIR__ . $sep . " Db " . $sep ;
$classes = [];
foreach ( glob ( $path . " Driver?*.php " ) as $file ) {
$name = basename ( $file , " .php " );
if ( substr ( $name , - 3 ) != " PDO " ) {
$name = NS_BASE . " Db \\ $name " ;
if ( class_exists ( $name )) {
$classes [ $name ] = $name :: driverName ();
}
2017-02-19 22:02:03 +00:00
}
2017-02-16 20:29:42 +00:00
}
return $classes ;
}
2016-10-06 02:08:43 +00:00
2017-02-16 20:29:42 +00:00
public function schemaVersion () : int {
return $this -> db -> schemaVersion ();
}
2016-10-15 13:45:23 +00:00
2017-02-16 20:29:42 +00:00
public function dbUpdate () : bool {
if ( $this -> db -> schemaVersion () < self :: SCHEMA_VERSION ) return $this -> db -> update ( self :: SCHEMA_VERSION );
return false ;
}
2016-10-18 15:42:21 +00:00
2017-02-16 20:29:42 +00:00
public function settingGet ( string $key ) {
$row = $this -> db -> prepare ( " SELECT value, type from newssync_settings where key = ? " , " str " ) -> run ( $key ) -> get ();
if ( ! $row ) return null ;
switch ( $row [ 'type' ]) {
case " int " : return ( int ) $row [ 'value' ];
case " numeric " : return ( float ) $row [ 'value' ];
case " text " : return $row [ 'value' ];
case " json " : return json_decode ( $row [ 'value' ]);
case " timestamp " : return date_create_from_format ( " ! " . self :: FORMAT_TS , $row [ 'value' ], new DateTimeZone ( " UTC " ));
case " date " : return date_create_from_format ( " ! " . self :: FORMAT_DATE , $row [ 'value' ], new DateTimeZone ( " UTC " ));
case " time " : return date_create_from_format ( " ! " . self :: FORMAT_TIME , $row [ 'value' ], new DateTimeZone ( " UTC " ));
case " bool " : return ( bool ) $row [ 'value' ];
case " null " : return null ;
default : return $row [ 'value' ];
}
}
2016-10-17 20:49:39 +00:00
2017-02-16 20:29:42 +00:00
public function settingSet ( string $key , $in , string $type = null ) : bool {
if ( ! $type ) {
switch ( gettype ( $in )) {
case " boolean " : $type = " bool " ; break ;
case " integer " : $type = " int " ; break ;
2017-02-19 22:02:03 +00:00
case " double " : $type = " numeric " ; break ;
2017-02-16 20:29:42 +00:00
case " string " :
2017-02-19 22:02:03 +00:00
case " array " : $type = " json " ; break ;
2017-02-16 20:29:42 +00:00
case " resource " :
case " unknown type " :
2017-02-19 22:02:03 +00:00
case " NULL " : $type = " null " ; break ;
2017-02-16 20:29:42 +00:00
case " object " :
if ( $in instanceof DateTimeInterface ) {
$type = " timestamp " ;
} else {
$type = " text " ;
}
break ;
2017-02-19 22:02:03 +00:00
default : $type = 'null' ; break ;
2017-02-16 20:29:42 +00:00
}
}
$type = strtolower ( $type );
switch ( $type ) {
case " integer " :
$type = " int " ;
case " int " :
$value =& $in ;
break ;
case " float " :
case " double " :
case " real " :
$type = " numeric " ;
case " numeric " :
$value =& $in ;
break ;
case " str " :
case " string " :
$type = " text " ;
case " text " :
$value =& $in ;
break ;
case " json " :
if ( is_array ( $in ) || is_object ( $in )) {
$value = json_encode ( $in );
} else {
$value =& $in ;
2017-02-19 22:02:03 +00:00
}
2017-02-16 20:29:42 +00:00
break ;
case " datetime " :
$type = " timestamp " ;
case " timestamp " :
if ( $in instanceof DateTimeInterface ) {
$value = gmdate ( self :: FORMAT_TS , $in -> format ( " U " ));
} else if ( is_numeric ( $in )) {
$value = gmdate ( self :: FORMAT_TS , $in );
} else {
$value = gmdate ( self :: FORMAT_TS , gmstrftime ( $in ));
}
break ;
case " date " :
if ( $in instanceof DateTimeInterface ) {
$value = gmdate ( self :: FORMAT_DATE , $in -> format ( " U " ));
} else if ( is_numeric ( $in )) {
$value = gmdate ( self :: FORMAT_DATE , $in );
} else {
$value = gmdate ( self :: FORMAT_DATE , gmstrftime ( $in ));
}
break ;
case " time " :
if ( $in instanceof DateTimeInterface ) {
$value = gmdate ( self :: FORMAT_TIME , $in -> format ( " U " ));
} else if ( is_numeric ( $in )) {
$value = gmdate ( self :: FORMAT_TIME , $in );
} else {
$value = gmdate ( self :: FORMAT_TIME , gmstrftime ( $in ));
}
break ;
case " boolean " :
case " bit " :
$type = " bool " ;
case " bool " :
$value = ( int ) $in ;
break ;
case " null " :
$value = null ;
break ;
default :
$type = " text " ;
$value =& $in ;
break ;
}
$this -> db -> prepare ( " REPLACE INTO newssync_settings(key,value,type) values(?,?,?) " , " str " , (( $type == " null " ) ? " null " : " str " ), " str " ) -> run ( $key , $value , " text " );
}
2016-10-17 20:49:39 +00:00
2017-02-16 20:29:42 +00:00
public function settingRemove ( string $key ) : bool {
$this -> db -> prepare ( " DELETE from newssync_settings where key = ? " , " str " ) -> run ( $key );
return true ;
}
2016-10-17 20:49:39 +00:00
2017-02-16 20:29:42 +00:00
public function userExists ( string $user ) : bool {
if ( ! $this -> data -> user -> authorize ( $user , __FUNCTION__ )) throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => $user ]);
return ( bool ) $this -> db -> prepare ( " SELECT count(*) from newssync_users where id is ? " , " str " ) -> run ( $user ) -> getSingle ();
}
2016-10-18 15:42:21 +00:00
2017-02-16 20:29:42 +00:00
public function userAdd ( string $user , string $password = null ) : bool {
if ( ! $this -> data -> user -> authorize ( $user , __FUNCTION__ )) throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => $user ]);
if ( $this -> userExists ( $user )) return false ;
if ( strlen ( $password ) > 0 ) $password = password_hash ( $password , \PASSWORD_DEFAULT );
2017-02-19 05:22:16 +00:00
$this -> db -> prepare ( " INSERT INTO newssync_users(id,password) values(?,?) " , " str " , " str " ) -> run ( $user , $password );
2017-02-16 20:29:42 +00:00
return true ;
}
2016-10-28 12:27:35 +00:00
2017-02-16 20:29:42 +00:00
public function userRemove ( string $user ) : bool {
if ( ! $this -> data -> user -> authorize ( $user , __FUNCTION__ )) throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => $user ]);
$this -> db -> prepare ( " DELETE from newssync_users where id is ? " , " str " ) -> run ( $user );
return true ;
}
2016-10-28 12:27:35 +00:00
2017-02-16 20:29:42 +00:00
public function userList ( string $domain = null ) : array {
if ( $domain !== null ) {
if ( ! $this -> data -> user -> authorize ( " @ " . $domain , __FUNCTION__ )) throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => $domain ]);
$domain = str_replace ([ " \\ " , " % " , " _ " ],[ " \\ \\ " , " \\ % " , " \\ _ " ], $domain );
$domain = " %@ " . $domain ;
$set = $this -> db -> prepare ( " SELECT id from newssync_users where id like ? " , " str " ) -> run ( $domain );
} else {
if ( ! $this -> data -> user -> authorize ( " " , __FUNCTION__ )) throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => " all users " ]);
$set = $this -> db -> prepare ( " SELECT id from newssync_users " ) -> run ();
}
$out = [];
foreach ( $set as $row ) {
$out [] = $row [ " id " ];
}
return $out ;
}
2017-02-19 22:02:03 +00:00
2017-02-16 20:29:42 +00:00
public function userPasswordGet ( string $user ) : string {
if ( ! $this -> data -> user -> authorize ( $user , __FUNCTION__ )) throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => $user ]);
if ( ! $this -> userExists ( $user )) return " " ;
return ( string ) $this -> db -> prepare ( " SELECT password from newssync_users where id is ? " , " str " ) -> run ( $user ) -> getSingle ();
}
2017-02-19 22:02:03 +00:00
2017-02-16 20:29:42 +00:00
public function userPasswordSet ( string $user , string $password = null ) : bool {
if ( ! $this -> data -> user -> authorize ( $user , __FUNCTION__ )) throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => $user ]);
if ( ! $this -> userExists ( $user )) return false ;
2017-02-19 05:22:16 +00:00
if ( strlen ( $password > 0 )) $password = password_hash ( $password , \PASSWORD_DEFAULT );
2017-02-16 20:29:42 +00:00
$this -> db -> prepare ( " UPDATE newssync_users set password = ? where id is ? " , " str " , " str " ) -> run ( $password , $user );
return true ;
}
2016-10-28 12:27:35 +00:00
2017-02-16 20:29:42 +00:00
public function userPropertiesGet ( string $user ) : array {
if ( ! $this -> data -> user -> authorize ( $user , __FUNCTION__ )) throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => $user ]);
$prop = $this -> db -> prepare ( " SELECT name,rights from newssync_users where id is ? " , " str " ) -> run ( $user ) -> get ();
if ( ! $prop ) return [];
return $prop ;
}
2016-10-28 12:27:35 +00:00
2017-02-16 20:29:42 +00:00
public function userPropertiesSet ( string $user , array & $properties ) : array {
if ( ! $this -> data -> user -> authorize ( $user , __FUNCTION__ )) throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => $user ]);
$valid = [ // FIXME: add future properties
2017-02-19 22:02:03 +00:00
" name " => " str " ,
2017-02-16 20:29:42 +00:00
];
if ( ! $this -> userExists ( $user )) return [];
$this -> db -> begin ();
foreach ( $valid as $prop => $type ) {
if ( ! array_key_exists ( $prop , $properties )) continue ;
2017-02-19 22:02:03 +00:00
$this -> db -> prepare ( " UPDATE newssync_users set $prop = ? where id is ? " , $type , " str " ) -> run ( $properties [ $prop ], $user );
2017-02-16 20:29:42 +00:00
}
$this -> db -> commit ();
return $this -> userPropertiesGet ( $user );
}
2016-11-04 02:54:27 +00:00
2017-02-16 20:29:42 +00:00
public function userRightsGet ( string $user ) : int {
if ( ! $this -> data -> user -> authorize ( $user , __FUNCTION__ )) throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => $user ]);
return ( int ) $this -> db -> prepare ( " SELECT rights from newssync_users where id is ? " , " str " ) -> run ( $user ) -> getSingle ();
}
2016-11-04 02:54:27 +00:00
2017-02-16 20:29:42 +00:00
public function userRightsSet ( string $user , int $rights ) : bool {
2017-02-19 05:22:16 +00:00
if ( ! $this -> data -> user -> authorize ( $user , __FUNCTION__ , $rights )) throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => $user ]);
2017-02-16 20:29:42 +00:00
if ( ! $this -> userExists ( $user )) return false ;
$this -> db -> prepare ( " UPDATE newssync_users set rights = ? where id is ? " , " int " , " str " ) -> run ( $rights , $user );
return true ;
}
2016-10-28 12:27:35 +00:00
2017-02-16 20:29:42 +00:00
public function subscriptionAdd ( string $user , string $url , string $fetchUser = " " , string $fetchPassword = " " ) : int {
2017-02-19 22:02:03 +00:00
// If the user isn't authorized to perform this action then throw an exception.
if ( ! $this -> data -> user -> authorize ( $user , __FUNCTION__ )) {
throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => $user ]);
}
// If the user doesn't exist throw an exception.
if ( ! $this -> userExists ( $user )) {
throw new User\Exception ( " doesNotExist " , [ " user " => $user , " action " => __FUNCTION__ ]);
}
2017-02-16 20:29:42 +00:00
$this -> db -> begin ();
2017-02-19 22:02:03 +00:00
2017-02-20 17:58:26 +00:00
// If the feed doesn't already exist in the database then add it to the database after determining its validity with PicoFeed.
2017-02-16 20:29:42 +00:00
$qFeed = $this -> db -> prepare ( " SELECT id from newssync_feeds where url is ? and username is ? and password is ? " , " str " , " str " , " str " );
$feed = $qFeed -> run ( $url , $fetchUser , $fetchPassword ) -> getSingle ();
2017-02-19 22:02:03 +00:00
if ( $feed === null ) {
try {
$reader = new Reader ;
$resource = $reader -> download ( $url );
$parser = $reader -> getParser (
$resource -> getUrl (),
$resource -> getContent (),
$resource -> getEncoding ()
);
$feed = $parser -> execute ();
} catch ( PicoFeedException $e ) {
2017-02-20 17:58:26 +00:00
// If there's any error while trying to download or parse the feed then return an exception.
2017-02-19 22:02:03 +00:00
throw new Feed\Exception ( $url , $e );
}
$this -> db -> prepare ( " INSERT INTO newssync_feeds(url,title,favicon,source,updated,modified,etag,username,password) values(?,?,?) " , " str " , " str " , " str " , " str " , " str " , " str " , " str " , " str " , " str " ) -> run (
$url ,
$feed -> title ,
2017-02-20 17:58:26 +00:00
// Grab the favicon for the Goodfeed; returns an empty string if it cannot find one.
2017-02-19 22:02:03 +00:00
( new PicoFeed\Reader\Favicon ) -> find ( $url ),
$feed -> siteUrl ,
2017-02-20 17:58:26 +00:00
// Convert the date formats to SQL date format before inserting.
2017-02-20 00:33:03 +00:00
$this -> driver :: formatDate ( $feed -> date ),
$this -> driver :: formatDate ( $resource -> getLastModified ()),
2017-02-19 22:02:03 +00:00
$resource -> getEtag (),
$fetchUser ,
$fetchPassword
);
2017-02-20 17:58:26 +00:00
// TODO: Populate newssync_articles with contents of what was obtained from PicoFeed.
// Get the ID for the feed that was just added.
$feedID = $qFeed -> run ( $url , $fetchUser , $fetchPassword ) -> getSingle ();
2017-02-16 20:29:42 +00:00
}
2017-02-19 22:02:03 +00:00
2017-02-20 17:58:26 +00:00
// Add the feed to the user's subscriptions.
$this -> db -> prepare ( " INSERT INTO newssync_subscriptions(owner,feed) values(?,?) " , " str " , " int " ) -> run ( $user , $feedID );
$sub = $this -> db -> prepare ( " SELECT id from newssync_subscriptions where owner is ? and feed is ? " , " str " , " int " ) -> run ( $user , $feedID ) -> getSingle ();
2017-02-16 20:29:42 +00:00
$this -> db -> commit ();
return $sub ;
}
2016-11-04 02:54:27 +00:00
2017-02-16 20:29:42 +00:00
public function subscriptionRemove ( int $id ) : bool {
$this -> db -> begin ();
$user = $this -> db -> prepare ( " SELECT owner from newssync_subscriptions where id is ? " , " int " ) -> run ( $id ) -> getSingle ();
if ( $user === null ) return false ;
if ( ! $this -> data -> user -> authorize ( $user , __FUNCTION__ )) throw new User\ExceptionAuthz ( " notAuthorized " , [ " action " => __FUNCTION__ , " user " => $user ]);
return ( bool ) $this -> db -> prepare ( " DELETE from newssync_subscriptions where id is ? " , " int " ) -> run ( $id ) -> changes ();
}
2016-10-15 13:45:23 +00:00
2016-10-02 21:07:17 +00:00
}