public function recent_comments() { $recent = new Rails\ActiveRecord\Collection(); # reverse_order to fetch last 6 comments # reversed in the last to return from lowest id if ($this->comments) { $recent->merge(array_slice($this->comments->members(), -6)); } return $recent; }
public static function find_all_by_url($url) { $url = ArtistUrl::normalize($url); $artists = new Rails\ActiveRecord\Collection(); while ($artists->blank() && strlen($url) > 10) { $u = str_replace('*', '%', $url) . '%'; $artists->merge(Artist::where("artists.alias_id IS NULL AND artists_urls.normalized_url LIKE ?", $u)->joins("JOIN artists_urls ON artists_urls.artist_id = artists.id")->order("artists.name")->take()); # Remove duplicates based on name $artists->unique('name'); $url = dirname($url); } return $artists->slice(0, 20); }
public function index() { $this->set_title('Notes'); if ($this->params()->post_id) { $this->posts = Post::where("id = ?", $this->params()->post_id)->order("last_noted_at DESC")->paginate($this->page_number(), 100); } else { $this->posts = Post::where("last_noted_at IS NOT NULL")->order("last_noted_at DESC")->paginate($this->page_number(), 16); } # iTODO: $this->respondTo(['html', 'xml' => function () { $notes = new Rails\ActiveRecord\Collection(); foreach ($this->posts as $post) { $notes->merge($post->notes); } $this->render(['xml' => $notes, 'root' => "notes"]); }, 'json' => function () { // {render :json => @posts.map {|x| x.notes}.flatten.to_json} }]); }
public function index() { $this->set_title('Comments'); if ($this->request()->format() == "json" || $this->request()->format() == "xml") { $this->comments = Comment::generate_sql($this->params()->all())->order("id DESC")->paginate($this->page_number(), 25); $this->respond_to_list("comments"); } else { $this->posts = Post::where("last_commented_at IS NOT NULL")->order("last_commented_at DESC")->paginate($this->page_number(), 10); $comments = new Rails\ActiveRecord\Collection(); $this->posts->each(function ($post) use($comments) { $comments->merge($post->recent_comments()); }); $newest_comment = $comments->max(function ($a, $b) { return $a->created_at > $b->created_at ? $a : $b; }); if (!current_user()->is_anonymous() && $newest_comment && current_user()->last_comment_read_at < $newest_comment->created_at) { current_user()->updateAttribute('last_comment_read_at', $newest_comment->created_at); } $this->posts->deleteIf(function ($x) { return !$x->can_be_seen_by(current_user(), array('show_deleted' => true)); }); } }
public static function similar_images($options = []) { $errors = []; $local_service = CONFIG()->local_image_service; $services = $options['services']; $services_by_server = []; foreach ($services as $service) { if (!isset(CONFIG()->image_service_list[$service]) || !($server = CONFIG()->image_service_list[$service])) { $errors[] = ['services' => [$service], 'message' => $service . " is an unknown service"]; continue; } if (!isset($services_by_server[$server])) { $services_by_server[$server] = []; } $services_by_server[$server][] = $service; } if (!$services_by_server) { return ['posts' => new Rails\ActiveRecord\Collection(), 'posts_external' => new Rails\ActiveRecord\Collection(), 'similarity' => [], 'services' => [], 'errors' => 'No service selected/no local service']; } # If the source is a local post, read the preview and send it with the request. if ($options['type'] == 'post') { $source_file = $options['source']->preview_path(); } elseif ($options['type'] == 'file') { $source_file = $options['source']; } $server_threads = []; $server_responses = []; $curl_opts = [CURLOPT_TIMEOUT => 5, CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true]; $mh = curl_multi_init(); $chk = -1; foreach ($services_by_server as $services_list) { $chk++; $search_url = null; if ($options['type'] == 'url') { $search_url = $options['source']; } if ($options['type'] == 'post' && CONFIG()->image_service_local_searches_use_urls) { $search_url = $options['source']['preview_url']; } $params = []; if ($search_url) { $params['url'] = $search_url; } else { if (function_exists('curl_file_create')) { // PHP v5.5.* fix $params['file'] = curl_file_create($source_file); } else { $params['file'] = '@' . $source_file; } } foreach ($services_list as $k => $s) { $params["service[{$k}]"] = $s; } $chn = 'ch' . $chk; ${$chn} = curl_init($server); curl_setopt_array(${$chn}, $curl_opts); curl_setopt(${$chn}, CURLOPT_POSTFIELDS, $params); curl_setopt(${$chn}, CURLOPT_CONNECTTIMEOUT, 4); curl_setopt(${$chn}, CURLOPT_HTTPHEADER, ['Host: ' . parse_url($server)['host']]); curl_multi_add_handle($mh, ${$chn}); } $ch_count = $chk; $active = null; do { $ret = curl_multi_exec($mh, $active); } while ($ret == CURLM_CALL_MULTI_PERFORM); while ($active && $ret == CURLM_OK) { if (curl_multi_select($mh) != -1) { usleep(100); } do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); } $posts = new Rails\ActiveRecord\Collection(); $posts_external = new Rails\ActiveRecord\Collection(); $similarity = []; $preview_url = ""; $next_id = 1; $server_list = array_keys($services_by_server); /** * Is there a class for PHP that can nicely handle XML? */ $get_attr = function ($xml, $attr) { $obj = $xml->attributes()->{$attr}; if ($obj) { $obj = (array) $obj; return $obj[0]; } return null; }; foreach (range(0, $ch_count) as $i) { $chn = 'ch' . $i; $server = $server_list[$i]; $resp = curl_multi_getcontent(${$chn}); if (!$resp) { $curl_err = curl_error(${$chn}); if (preg_match('/^Operation timed out/', $curl_err)) { $err_msg = 'timed out'; Rails::log()->notice("[SimilarImages] cURL timed out: " . $curl_err); } else { $err_msg = 'empty response'; Rails::log()->warning(sprintf("[SimilarImages] cURL error: (%s) %s", curl_errno(${$chn}), $curl_err)); } $errors[$server] = ['message' => $err_msg]; continue; } try { $doc = new SimpleXMLElement($resp); } catch (Exception $e) { ob_start(); var_dump(curl_getinfo(${$chn})); $info = ob_get_clean(); Rails::log()->error("Similar Images Error\ncURL Error: " . curl_error(${$chn}) . "\ncURL Info:\n" . $info); Rails::log()->exception($e); $errors[$server] = ['message' => 'parse error']; continue; } if ($doc->getName() == 'error') { $errors[$server] = ['message' => $doc->message]; continue; } elseif ($doc->getName() != 'matches') { $errors[$server] = ['message' => 'invalid response']; continue; } $threshold = !empty($options['threshold']) ? $options['threshold'] : (double) $get_attr($doc, 'threshold'); foreach ($doc->match as $element) { $sim = (double) $get_attr($element, 'sim'); if ($sim >= $threshold and $sim > 0) { $service = $get_attr($element, 'service'); $image = $element->post; $id = $get_attr($image, 'id'); $md5 = $get_attr($image, 'md5'); if ($service == $local_service) { $post = Post::where('id = ?', $id); if ($post && is_object($options['source']) && $post->id != $options['source']->id) { $posts[] = $post; $similarity[spl_object_hash($post)] = $sim; } } elseif ($service) { $post = new ExternalPost(); $post->id = (string) $next_id; $next_id++; $post->md5 = $md5; $post->preview_url = $get_attr($element, 'preview'); if ($service == 'gelbooru.com') { # hack $post->url = "http://" . $service . "/index.php?page=post&s=view&id=" . $id; } elseif ($service == "e-shuushuu.net") { # hack $post->url = "http://" . $service . "/image/" . $id . "/"; } else { $post->url = "http://" . $service . "/post/show/" . $id; } $post->sample_url = $get_attr($image, 'sample_url') ?: $post->url; $post->service = $service; $post->width = $get_attr($image, 'width'); $post->height = $get_attr($image, 'height'); $post->tags = $get_attr($image, 'tags') ?: ''; if (empty($options['data_search'])) { $post->rating = $get_attr($image, 'rating') ?: 's'; } else { $post->rating = $get_attr($image, 'rating') ?: false; } # Extra attributes. if (!empty($options['data_search'])) { $post->original_preview_url = $get_attr($image, 'preview_url'); $post->id = $get_attr($image, 'id'); $post->author = $get_attr($image, 'author'); $post->created_at = $get_attr($image, 'created_at'); $post->creator_id = $get_attr($image, 'creator_id'); $post->file_size = $get_attr($image, 'file_size'); $post->file_url = $get_attr($image, 'file_url'); $post->score = $get_attr($image, 'score'); $post->source = $get_attr($image, 'source'); $post->icon_path = ExternalPost::get_service_icon($service); if (preg_match('/\\.png$/', $post->file_url)) { $post->has_png = true; } } $posts_external[] = $post; $similarity[spl_object_hash($post)] = $sim; } } } } $posts->sort(function ($a, $b) { $aid = spl_object_hash($a); $bid = spl_object_hash($b); if ($similarity[$aid] == $similarity[$bid]) { return 0; } elseif ($similarity[$aid] > $similarity[$bid]) { return 1; } return -1; }); foreach ($errors as $server => $error) { if (empty($error['services'])) { $error['services'] = !empty($services_by_server[$server]) ? $services_by_server[$server] : $server; } } $ret = ['posts' => $posts, 'posts_external' => $posts_external, 'similarity' => $similarity, 'services' => $services, 'errors' => $errors]; if ($options['type'] == 'post') { $ret['source'] = $options['source']; $ret['similarity'][spl_object_hash($options['source'])] = 'Original'; $ret['search_id'] = $ret['source']->id; } else { $post = new ExternalPost(); # $post->md5 = $md5; $post->preview_url = $options['source_thumb']; if (!empty($options['full_url'])) { $post->url = $options['full_url']; } elseif (!empty($options['url'])) { $post->url = $options['url']; } elseif (!empty($options['source_thumb'])) { $post->url = $options['source_thumb']; } $post->id = 'source'; $post->rating = 'q'; $ret['search_id'] = 'source'; # Don't include the source URL if it's a data: url; it can be very large and isn't useful. if (substr($post->url, 0, 5) == "data:") { $post->url = ""; } list($source_width, $source_height) = getimagesize($source_file); # Since we lose access to the original image when we redirect to a saved search, # the original dimensions can be passed as parameters so we can still display # the original size. This can also be used by user scripts to include the # size of the real image when a thumbnail is passed. $post->width = !empty($options['width']) ? $options['width'] : $source_width; $post->height = !empty($options['height']) ? $options['height'] : $source_height; $ret['external_source'] = $post; $ret['similarity'][spl_object_hash($post)] = "Original"; } return $ret; }
public function moderate() { $this->set_title('Moderation Queue'); if ($this->request()->isPost()) { $posts = new Rails\ActiveRecord\Collection(); if ($this->params()->ids) { foreach (array_keys($this->params()->ids) as $post_id) { $post = Post::find($post_id); if ($this->params()->commit == "Approve") { $post->approve(current_user()->id); } elseif ($this->params()->commit == "Delete") { $post->destroy_with_reason($this->params()->reason ? $this->params()->reason : $this->params()->reason2, current_user()); # Include post data for the parent: deleted posts aren't counted as children, so # their has_children attribute may change. if ($post->parent_id) { $posts[] = $post->get_parent(); } } # Post may have been permanently deleted. if (!CONFIG()->delete_posts_permanently) { $post->reload(); } $posts[] = $post; } } $posts->unique(); if ($this->request()->format() == "json" || $this->request()->format() == "xml") { $api_data = Post::batch_api_data($posts->members()); } else { $api_data = array(); } if ($this->params()->commit == "Approve") { $this->respond_to_success("Post approved", "#moderate", array('api' => $api_data)); } elseif ($this->params()->commit == "Delete") { $this->respond_to_success("Post deleted", "#moderate", array('api' => $api_data)); } } else { if ($this->params()->query) { list($sql, $params) = Post::generate_sql($this->params()->query, array('pending' => true, 'order' => "id desc")); $this->pending_posts = Post::findBySql($sql, $params); list($sql, $params) = Post::generate_sql($this->params()->query, array('flagged' => true, 'order' => "id desc")); $this->flagged_posts = Post::findBySql($sql, $params); } else { $this->pending_posts = Post::where("status = 'pending'")->order("id desc")->take(); $this->flagged_posts = Post::where("status = 'flagged'")->order("id desc")->take(); } } }