Use classmap autoloader for stapibas classes
[stapibas.git] / src / stapibas / Feed / PingUrls.php
1 <?php
2 namespace stapibas;
3
4 /**
5  * Pings all URLs that have not been pinged yet
6  */
7 class Feed_PingUrls
8 {
9     public $db;
10     public $log;
11     public $pbc;
12
13     public function __construct(Dependencies $deps)
14     {
15         $this->deps = $deps;
16         $this->db   = $deps->db;
17         $this->log  = $deps->log;
18
19         $this->pbc = new \PEAR2\Services\Linkback\Client();
20         $req = $this->pbc->getRequest();
21         $req->setConfig(
22             array(
23                 'ssl_verify_peer' => false,
24                 'ssl_verify_host' => false
25             )
26         );
27         $this->pbc->setRequestTemplate($req);
28         $headers = $req->getHeaders();
29         $req->setHeader('user-agent', 'stapibas / ' . $headers['user-agent']);
30
31         $this->pbc->setDebug(true);
32     }
33
34     public function pingAll()
35     {
36         $this->log->info('Pinging all URLs..');
37         $res = $this->db->query(
38             'SELECT fe_url, feu_id, feu_url, feu_tries'
39             . ' FROM feedentries, feedentryurls'
40             . ' WHERE fe_id = feu_fe_id' . $this->sqlNeedsUpdate()
41         );
42         $items = 0;
43         while ($row = $res->fetch(\PDO::FETCH_OBJ)) {
44             $this->log->info(
45                 'Pinging URL #%d: %s', $row->feu_id, $row->feu_url
46             );
47             $this->ping($row);
48             ++$items;
49         }
50         $this->log->info('Finished pinging %d URLs.', $items);
51     }
52
53     public function pingSome($urlOrIds)
54     {
55         $options = array();
56         foreach ($urlOrIds as $urlOrId) {
57             if (is_numeric($urlOrId)) {
58                 $options[] = 'feu_id = ' . intval($urlOrId);
59             } else {
60                 $options[] = 'feu_url = ' . $this->db->quote($urlOrId);
61             }
62         }
63
64         $this->log->info('Pinging %d URLs..', count($options));
65         $res = $this->db->query(
66             'SELECT fe_url, feu_id, feu_url, feu_tries'
67             . ' FROM feedentries, feedentryurls'
68             . ' WHERE fe_id = feu_fe_id'
69             . $this->sqlNeedsUpdate()
70             . ' AND (' . implode(' OR ', $options) . ')'
71         );
72         $items = 0;
73         while ($row = $res->fetch(\PDO::FETCH_OBJ)) {
74             $this->log->info(
75                 'Pinging URL #%d: %s', $row->feu_id, $row->feu_url
76             );
77             $this->ping($row);
78             ++$items;
79         }
80         $this->log->info('Finished pinging %d URLs.', $items);
81     }
82
83     public function ping($row)
84     {
85         $from = $row->fe_url;
86         $to   = $row->feu_url;
87
88         try {
89             $res = $this->pbc->send($from, $to);
90         } catch (\Exception $e) {
91             $this->log->err('Exception: ' . $e->getMessage());
92             $this->db->exec(
93                 'UPDATE feedentryurls SET'
94                 . '  feu_pinged = 1'
95                 . ', feu_updated = NOW()'
96                 . ', feu_error = 1'
97                 . ', feu_error_code = ' . $this->db->quote($e->getCode())
98                 . ', feu_error_message = ' . $this->db->quote($e->getMessage())
99                 . ', feu_tries = ' . $this->db->quote($row->feu_tries + 1)
100                 . ', feu_retry = ' . $this->sqlRetry($e)
101                 . ' WHERE feu_id = ' . $this->db->quote($row->feu_id)
102             );
103             return;
104         }
105
106         if (!$res->isError()) {
107             //all fine
108             $this->log->info('ok');
109             $this->db->exec(
110                 'UPDATE feedentryurls SET'
111                 . '  feu_pinged = 1'
112                 . ', feu_updated = NOW()'
113                 . ', feu_error = 0'
114                 . ', feu_error_code = ""'
115                 . ', feu_error_message = ""'
116                 . ', feu_tries = ' . $this->db->quote($row->feu_tries + 1)
117                 . ', feu_retry = 0'
118                 . ' WHERE feu_id = ' . $this->db->quote($row->feu_id)
119             );
120         } else {
121             //error
122             $code = $res->getCode();
123             $this->log->err('Error: ' . $code . ': ' . $res->getMessage());
124             $httpRes = $res->getResponse();
125             if ($httpRes) {
126                 $this->log->info(
127                     'Pingback response: Status code ' . $httpRes->getStatus()
128                     . ', headers: ' . print_r($httpRes->getHeader(), true)
129                 );
130                 if ($code == 100 || $code == 101 || $code == -32600) {
131                     $this->log->info('HTTP body: ' . $httpRes->getBody());
132                 }
133             }
134             $this->db->exec(
135                 'UPDATE feedentryurls SET'
136                 . '  feu_pinged = 1'
137                 . ', feu_updated = NOW()'
138                 . ', feu_error = 1'
139                 . ', feu_error_code = ' . $this->db->quote($res->getCode())
140                 . ', feu_error_message = ' . $this->db->quote($res->getMessage())
141                 . ', feu_tries = ' . $this->db->quote($row->feu_tries + 1)
142                 . ', feu_retry = ' . $this->sqlRetry($res)
143                 . ' WHERE feu_id = ' . $this->db->quote($row->feu_id)
144             );
145         }
146     }
147
148     protected function sqlNeedsUpdate()
149     {
150         if ($this->deps->options['force']) {
151             return '';
152         }
153         $sqlRetry = '(feu_retry = 1 AND feu_tries < 5)';
154         //FIXME: wait at least 1 hour before retrying
155
156         return ' AND feu_active = 1 AND (feu_pinged = 0 OR ' . $sqlRetry . ')';
157     }
158
159     /**
160      * Determines if it should be retried to pingback the URL after some time
161      *
162      * @param $obj mixed Exception or Pingback response
163      */
164     protected function sqlRetry($obj)
165     {
166         if ($obj instanceof \Exception) {
167             return '1';
168         }
169
170         switch ($obj->getCode()) {
171         case -32601:  //they have xmp-rpc, but do not support pingback
172         case 17:  //they think we don't link to them
173         case 18:  //they think we send out pingback spam
174         case 48:  //already registered
175         case 49:  //access denied
176         case 200: //pingback not supported
177         case 201: //Unvalid target URI
178             return '0';
179         }
180
181         return '1';
182     }
183 }
184 ?>