Fully enabled tag subscriptions.

This commit is contained in:
Parziphal 2014-01-12 07:05:06 -05:00
parent 1bd69c151e
commit 827072db46
17 changed files with 328 additions and 30 deletions

View File

@ -0,0 +1,63 @@
<?php
class TagSubscriptionController extends ApplicationController
{
protected function filters()
{
return [
'before' => [
'member_only' => ['except' => ['index']],
'no_anonymous'
]
];
}
public function create()
{
$this->response()->headers()->setContentType('text/javascript');
$this->setLayout(false);
if ($this->request()->isPost()) {
if (current_user()->tag_subscriptions->size() >= CONFIG()->max_tag_subscriptions) {
$this->tag_subscription = null;
} else {
$this->tag_subscription = TagSubscription::create(['user_id' => current_user()->id, 'tag_query' => '']);
}
}
}
public function update()
{
if ($this->request()->isPost()) {
if (is_array($this->params()->tag_subscription)) {
foreach ($this->params()->tag_subscription as $tag_subscription_id => $ts) {
$tag_subscription = TagSubscription::find($tag_subscription_id);
if ($tag_subscription->user_id == current_user()->id) {
$tag_subscription->updateAttributes($ts);
}
}
}
}
$this->notice("Tag subscriptions updated");
$this->redirectTo('user#edit');
}
public function index()
{
$this->tag_subscriptions = current_user()->tag_subscriptions;
}
public function destroy()
{
$this->response()->headers()->setContentType('text/javascript');
$this->setLayout(false);
if ($this->request()->isPost()) {
$this->tag_subscription = TagSubscription::find($this->params()->id);
if (current_user()->has_permission($this->tag_subscription)) {
$this->tag_subscription->destroy();
}
}
}
}

View File

@ -0,0 +1,20 @@
<?php
class TagSubscriptionHelper extends Rails\ActionView\Helper
{
public function tag_subscription_listing($user)
{
$html = [];
foreach ($user->tag_subscriptions as $tag_subscription) {
$h = '<span class="group"><strong>' . $this->linkTo($tag_subscription->name, ["post#", 'tags' => "sub:" . $user->name . ":" . $tag_subscription->name]) . "</strong>:";
$tags = preg_split('/\s+/', $tag_subscription->tag_query);
sort($tags);
$group = [];
foreach ($tags as $tag) {
$group[] = $this->linkTo($tag, ['post#', 'tags' => $tag]);
}
$h .= join(' ', $group);
$html[] = $h;
}
return join(' ', $html);
}
}

View File

@ -42,9 +42,9 @@ class JobTask extends Rails\ActiveRecord\Base
return "start: " . $ti->predicate->name . ", result: " . $ti->consequent->name; return "start: " . $ti->predicate->name . ", result: " . $ti->consequent->name;
break; break;
// case "calculate_tag_subscriptions" case "calculate_tag_subscriptions":
// last_run = data["last_run"] return "last run: " . $this->data->last_run;
// "last run:#{last_run}" break;
// case "upload_posts_to_mirrors" // case "upload_posts_to_mirrors"
// ret = "" // ret = ""
@ -255,6 +255,16 @@ class JobTask extends Rails\ActiveRecord\Base
$ti->approve($updater_id, $updater_ip_addr); $ti->approve($updater_id, $updater_ip_addr);
} }
public function execute_calculate_tag_subscriptions()
{
if (Rails::cache()->read("delay-tag-sub-calc")) {
return;
}
Rails::cache()->write("delay-tag-sub-calc", ['expires_in' => '360 minutes']);
TagSubscription::process_all();
$this->updateAttributes(['data' => ['last_run' => date('Y-m-d H:i')]]);
}
protected function init() protected function init()
{ {
$this->setData($this->data_as_json ? json_decode($this->data_as_json) : new stdClass()); $this->setData($this->data_as_json ? json_decode($this->data_as_json) : new stdClass());

View File

@ -164,18 +164,19 @@ trait PostSqlMethods
$cond_params[] = $q['source']; $cond_params[] = $q['source'];
} }
// if (is_string($q['subscriptions'])) { if (isset($q['subscriptions'])) {
// preg_match('/^(.+?):(.+)$/', $q['subscriptions'], $m); preg_match('/^(.+?):(.+)$/', $q['subscriptions'], $m);
// $username = $m[1] || $q['subscriptions']; $username = $m[1] ?: $q['subscriptions'];
// $subscription_name = $m[2]; $subscription_name = $m[2];
// $user = new User('find_by_name', $username); $user = User::find_by_name($username);
// if ($user) { if ($user) {
// $paramsost_ids = TagSubscription.find_post_ids(user.id, subscription_name) if ($post_ids = TagSubscription::find_post_ids($user->id, $subscription_name)) {
// $conds[] = "p.id IN (?)" $conds[] = 'p.id IN (?)';
// $cond_params[] = post_ids $cond_params[] = $post_ids;
// } }
// } }
}
if (is_string($q['fav'])) { if (is_string($q['fav'])) {
$joins[] = "JOIN favorites f ON f.post_id = p.id JOIN users fu ON f.user_id = fu.id"; $joins[] = "JOIN favorites f ON f.post_id = p.id JOIN users fu ON f.user_id = fu.id";

112
app/models/TagSubscription.php Executable file
View File

@ -0,0 +1,112 @@
<?php
class TagSubscription extends Rails\ActiveRecord\Base
{
protected function associations()
{
return [
'belongs_to' => [
'user'
]
];
}
protected function callbacks()
{
return [
'before_save' => [
'normalize_name'
],
'before_create' => [
'initialize_post_ids'
]
];
}
protected function scopes()
{
return [
'visible' => function() {
$this->where(['is_visible_on_profile' => true]);
}
];
}
public function normalize_name()
{
/**
* MI: \P{Word} doesn't exist in PHP so I used \P{L}, which matches a single letter.
* In order for \P{L} to work, I had to use mb_conver_encoding
* which splits a single mb character into two, thus the second replace.
*/
$this->name = preg_replace(['/\P{L}/', '/_{2,}/'], '_', mb_convert_encoding($this->name, 'ISO-8859-2'));
}
public function initialize_post_ids()
{
if ($this->user->is_privileged_or_higher()) {
$this->cached_post_ids = join(',', array_unique(Post::find_by_tags($this->tag_query, ['limit' => ceil(CONFIG()->tag_subscription_post_limit / 3), 'select' => 'p.id', 'order' => 'p.id desc'])->getAttributes('id')));
}
}
static public function find_post_ids($user_id, $name = null, $limit = null)
{
if (!$limit) {
$limit = CONFIG()->tag_subscription_post_limit;
}
$post_ids = self::select('cached_post_ids')->where(['user_id' => $user_id]);
if ($name) {
$post_ids->where('name LIKE ?', $name . '%');
}
$post_ids = $post_ids->take();
$parsed_ids = [];
foreach ($post_ids as $subs) {
$ids = explode(',', $subs->cached_post_ids);
foreach ($ids as &$id) {
$id = (int)$id;
}
$parsed_ids = array_merge($parsed_ids, $ids);
}
sort($parsed_ids);
return array_slice(array_reverse(array_unique($parsed_ids)), 0, $limit);
}
static public function find_posts($user_id, $name = null, $limit = null)
{
return Post::available()->where('id IN (?)', self::find_post_ids($user_id, $name, $limit))->order('id DESC')->take();
}
static public function process_all()
{
foreach (self::all() as $tag_subscription) {
if ($tag_subscription->user->is_privileged_or_higher()) {
try {
self::transaction(function() use ($tag_subscription) {
$tags = preg_split('/\s+/', $tag_subscription->tag_query);
$post_ids = [];
foreach ($tags as $tag) {
$post_ids = array_merge(
$post_ids,
Post::find_by_tags(
$tag,
[
'limit' => ceil(CONFIG()->tag_subscription_post_limit / 3),
'select' => 'p.id',
'order' => 'p.id desc'
]
)->getAttributes('id')
);
}
sort($post_ids);
$tag_subscription->updateAttribute('cached_post_ids', join(',', array_unique(array_slice(array_reverse($post_ids), 0, CONFIG()->tag_subscription_post_limit))));
});
} catch (Exception $e) {
# fail silently
}
sleep(1);
}
}
}
}

View File

@ -18,11 +18,6 @@ class User extends Rails\ActiveRecord\Base
*/ */
public $ip_addr; public $ip_addr;
/**
* iTODO: Temp property while tag subs aren't enabled.
*/
public $tag_subscriptions;
protected $post_count; protected $post_count;
static public function set_current_user(User $user) static public function set_current_user(User $user)
@ -638,9 +633,10 @@ class User extends Rails\ActiveRecord\Base
// tag_subscriptions_text.map(&:tag_query).sort.join(" ") // tag_subscriptions_text.map(&:tag_query).sort.join(" ")
// end // end
// def tag_subscription_posts(limit, name) public function tag_subscription_posts($limit, $name)
// TagSubscription.find_posts(id, name, limit) {
// end return TagSubscription::find_posts($this->id, $name, $limit);
}
# } # }
# UserLanguageMethods { # UserLanguageMethods {

View File

@ -0,0 +1,24 @@
<thead>
<tr>
<th></th>
<th width="15%"><?=$this->t('sub_name') ?></th>
<th width="70%"><?=$this->t('sub_tags') ?></th>
<th width="10%"><?=$this->t('sub_vis') ?></th>
</tr>
</thead>
<tfoot>
<tr>
<td></td>
<td colspan="3">
<?= $this->submitTag($this->t('sub_save')) ?>
<input onclick="new Ajax.Request('<?= $this->urlFor(['tag_subscription#create', 'format' => 'js']) ?>', {asynchronous:true, evalScripts:true});" type="button" value="<?= $this->t('sub_add') ?>">
</td>
</tr>
</tfoot>
<tbody id="tag-subscription-body">
<?php foreach ($this->tag_subscriptions as $tag_subscription) : ?>
<?= $this->partial("listing_row", ['tag_subscription' => $tag_subscription]) ?>
<?php endforeach ?>
</tbody>

View File

@ -0,0 +1,8 @@
<tr id="tag-subscription-row-<?= $this->tag_subscription->id ?>">
<td><input onclick="new Ajax.Request('<?= $this->urlFor(['tag_subscription#destroy', 'id' => $this->tag_subscription->id, 'format' => 'js']) ?>', {asynchronous:true, evalScripts:true});" type="button" value="<?= '-' ?>"></td>
<td><?= $this->textFieldTag("tag_subscription[".$this->tag_subscription->id."][name]", $this->tag_subscription->name, ['size' => 20]) ?></td>
<td><?= $this->textFieldTag("tag_subscription[".$this->tag_subscription->id."][tag_query]", $this->tag_subscription->tag_query, ['size' => 70]) ?></td>
<td>
<?= $this->selectTag("tag_subscription[".$this->tag_subscription->id."][is_visible_on_profile]", $this->optionsForSelect(["Visible" => 1, "Hidden" => 0], $this->tag_subscription->is_visible_on_profile)) ?>
</td>
</tr>

View File

@ -1,7 +1,7 @@
<?php if (empty($this->user->tag_subscriptions)) : ?> <?php if ($this->user->tag_subscriptions->none()) : ?>
<?= $this->t('sub_none') ?> <?= $this->t('sub_none') ?>
<?php else: ?> <?php else: ?>
<?= $this->tag_subscription_listing($user) ?> <?= $this->tag_subscription_listing($this->user) ?>
<?php endif ?> <?php endif ?>
<?php if (current_user()->id == $this->user->id) : ?> <?php if (current_user()->id == $this->user->id) : ?>

View File

@ -0,0 +1,5 @@
<?php if ($this->tag_subscription) : ?>
$('tag-subscription-body').insert({ bottom: '<?= $this->escapeJavascript($this->partial('listing_row', ['tag_subscription' => $this->tag_subscription])) ?>'});
<?php else: ?>
notice('<?= "You can only create up to " . CONFIG()->max_tag_subscriptions . " tag subscriptions" ?>');
<?php endif ?>

View File

@ -0,0 +1,2 @@
$('<?= $this->escapeJavascript("tag-subscription-row-" . $this->tag_subscription->id) ?>').remove();
notice('Tag subscription deleted');

View File

@ -0,0 +1,10 @@
<?= $this->formTag("#update", function() { ?>
<h4><?= $this->t('sub_edit') ?></h4>
<div style="margin-bottom: 1em;">
<?=$this->t('sub_text') ?><?= CONFIG()->max_tag_subscriptions ?><?=$this->t('sub_text2') ?>
</div>
<table width="100%" class="highlightable">
<?= $this->partial("listing", ['tag_subscriptions' => $this->tag_subscriptions]) ?>
</table>
<?php }) ?>

View File

@ -1,3 +1,8 @@
<?php
// vpe(
// $this->user->tag_subscriptions->select(function($x) { return (bool)$x->is_visible_on_profile; })
// )
?>
<?php if ($this->user->has_avatar()) : ?> <?php if ($this->user->has_avatar()) : ?>
<div style="width: 25em; height: <?= max($this->user->avatar_height, 80) ?>px; position: relative;"> <div style="width: 25em; height: <?= max($this->user->avatar_height, 80) ?>px; position: relative;">
<div style="position: absolute; bottom: 0;"> <div style="position: absolute; bottom: 0;">
@ -143,14 +148,12 @@
</table> </table>
</div> </div>
<?php /* <?php foreach($this->user->tag_subscriptions->select(function($x) { return (bool)$x->is_visible_on_profile; }) as $tag_subscription) : ?>
<?php $this->user->tag_subscriptions.visible.each do |tag_subscription| ?>
<div style="margin-bottom: 1em; float: left; clear: both;"> <div style="margin-bottom: 1em; float: left; clear: both;">
<h4><?= $this->t('user_sub2') ?><?= tag_subscription.name ?> <?= $this->linkTo("»", 'post#index', 'tags' => 'sub:#array($this->user->name}:#{tag_subscription.name)' ?></h4> <h4><?= $this->t('user_sub2') ?><?= $tag_subscription->name ?> <?= $this->linkTo("»", ['post#index', 'tags' => 'sub:' . $this->user->name . ':' . $tag_subscription->name]) ?></h4>
<?= $this->partial("post/posts", array('posts' => $this->user->tag_subscription_posts(5, tag_subscription.name).select array(|x| CONFIG()->can_see_post.call(current_user(), x)))) ?> <?= $this->partial("post/posts", array('posts' => $this->user->tag_subscription_posts(5, $tag_subscription->name)->select(function($x) { return CONFIG()->can_see_post(current_user(), $x); }))) ?>
</div> </div>
<?php end ?> <?php endforeach ?>
*/ ?>
<div style="margin-bottom: 1em; float: left; clear: both;"> <div style="margin-bottom: 1em; float: left; clear: both;">
<h4><?= $this->linkTo($this->t('user_fav3'), array('post#index', 'tags' => 'vote:3:'.$this->user->name.' order:vote')) ?></h4> <h4><?= $this->linkTo($this->t('user_fav3'), array('post#index', 'tags' => 'vote:3:'.$this->user->name.' order:vote')) ?></h4>

View File

@ -260,6 +260,12 @@ abstract class DefaultConfig
# Use this config to enable Google Analytics. Fill in the GA Tracking ID (like 'UA-XXXXX-X') # Use this config to enable Google Analytics. Fill in the GA Tracking ID (like 'UA-XXXXX-X')
public $ga_tracking_id = ''; public $ga_tracking_id = '';
# Max number of posts to cache
public $tag_subscription_post_limit = 200;
# Max number of fav tags per user
public $max_tag_subscriptions = 5;
/** /**
* ******************************* * *******************************
* MyImouto-specific configuration * MyImouto-specific configuration
@ -305,6 +311,7 @@ abstract class DefaultConfig
'upload_batch_posts', 'upload_batch_posts',
'approve_tag_implication', 'approve_tag_implication',
'approve_tag_alias', 'approve_tag_alias',
'calculate_tag_subscriptions'
]; ];
# Javascripts assets manifest files. # Javascripts assets manifest files.
@ -500,4 +507,12 @@ abstract class DefaultConfig
return 'https://yande.re/post/show/'.$m[1]; return 'https://yande.re/post/show/'.$m[1];
} }
} }
public $external_storage = null;
public function es_create($model)
{
$className = $this->external_storage . '\Models\\' . get_class($model);
$className::create($model);
}
} }

View File

@ -0,0 +1,13 @@
<?php
class InsertTagSubJobTask extends Rails\ActiveRecord\Migration\Base
{
public function up()
{
JobTask::create([
'task_type' => "calculate_tag_subscriptions",
'data_as_json' => '{}',
'status' => "pending",
'repeat_count' => -1
]);
}
}

View File

@ -0,0 +1,8 @@
<?php
class AddUserIdFkToTagSubs extends Rails\ActiveRecord\Migration\Base
{
public function up()
{
$this->execute("ALTER TABLE tag_subscriptions ADD CONSTRAINT fk_tag_subs__user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE");
}
}

View File

@ -0,0 +1,8 @@
<?php
class ChangeTagSubsCharset extends Rails\ActiveRecord\Migration\Base
{
public function up()
{
$this->execute("ALTER TABLE `tag_subscriptions` CHANGE `tag_query` `tag_query` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, CHANGE `cached_post_ids` `cached_post_ids` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, CHANGE `name` `name` VARCHAR( 32 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL");
}
}